1 /// Connect to a MySQL/MariaDB server.
2 module mysql.connection;
3 
4 import std.algorithm;
5 import std.conv;
6 import std.exception;
7 import std.range;
8 import std.socket;
9 import std.string;
10 import std.typecons;
11 
12 import mysql.commands;
13 import mysql.exceptions;
14 import mysql.prepared;
15 import mysql.protocol.comms;
16 import mysql.protocol.constants;
17 import mysql.protocol.packets;
18 import mysql.protocol.sockets;
19 import mysql.result;
20 debug(MYSQLN_TESTS)
21 {
22 	import mysql.test.common;
23 }
24 
25 version(Have_vibe_d_core)
26 {
27 	static if(__traits(compiles, (){ import vibe.core.net; } ))
28 		import vibe.core.net;
29 	else
30 		static assert(false, "mysql-native can't find Vibe.d's 'vibe.core.net'.");
31 }
32 
33 /// The default `mysql.protocol.constants.SvrCapFlags` used when creating a connection.
34 immutable SvrCapFlags defaultClientFlags =
35 		SvrCapFlags.OLD_LONG_PASSWORD | SvrCapFlags.ALL_COLUMN_FLAGS |
36 		SvrCapFlags.WITH_DB | SvrCapFlags.PROTOCOL41 |
37 		SvrCapFlags.SECURE_CONNECTION;// | SvrCapFlags.MULTI_STATEMENTS |
38 		//SvrCapFlags.MULTI_RESULTS;
39 
40 /++
41 Submit an SQL command to the server to be compiled into a prepared statement.
42 
43 This will automatically register the prepared statement on the provided connection.
44 The resulting `mysql.prepared.Prepared` can then be used freely on ANY `Connection`,
45 as it will automatically be registered upon its first use on other connections.
46 Or, pass it to `Connection.register` if you prefer eager registration.
47 
48 Internally, the result of a successful outcome will be a statement handle - an ID -
49 for the prepared statement, a count of the parameters required for
50 execution of the statement, and a count of the columns that will be present
51 in any result set that the command generates.
52 
53 The server will then proceed to send prepared statement headers,
54 including parameter descriptions, and result set field descriptions,
55 followed by an EOF packet.
56 
57 Throws: `mysql.exceptions.MYX` if the server has a problem.
58 +/
59 Prepared prepare(Connection conn, const(char[]) sql)
60 {
61 	auto info = conn.registerIfNeeded(sql);
62 	return Prepared(sql, info.headers, info.numParams);
63 }
64 
65 /++
66 This function is provided ONLY as a temporary aid in upgrading to mysql-native v2.0.0.
67 
68 See `BackwardCompatPrepared` for more info.
69 +/
70 deprecated("This is provided ONLY as a temporary aid in upgrading to mysql-native v2.0.0. You should migrate from this to the Prepared-compatible exec/query overloads in 'mysql.commands'.")
71 BackwardCompatPrepared prepareBackwardCompat(Connection conn, const(char[]) sql)
72 {
73 	return prepareBackwardCompatImpl(conn, sql);
74 }
75 
76 /// Allow mysql-native tests to get around the deprecation message
77 package BackwardCompatPrepared prepareBackwardCompatImpl(Connection conn, const(char[]) sql)
78 {
79 	return BackwardCompatPrepared(conn, prepare(conn, sql));
80 }
81 
82 /++
83 Convenience function to create a prepared statement which calls a stored function.
84 
85 Be careful that your `numArgs` is correct. If it isn't, you may get a
86 `mysql.exceptions.MYX` with a very unclear error message.
87 
88 Throws: `mysql.exceptions.MYX` if the server has a problem.
89 
90 Params:
91 	name = The name of the stored function.
92 	numArgs = The number of arguments the stored procedure takes.
93 +/
94 Prepared prepareFunction(Connection conn, string name, int numArgs)
95 {
96 	auto sql = "select " ~ name ~ preparedPlaceholderArgs(numArgs);
97 	return prepare(conn, sql);
98 }
99 
100 ///
101 @("prepareFunction")
102 debug(MYSQLN_TESTS)
103 unittest
104 {
105 	import mysql.test.common;
106 	mixin(scopedCn);
107 
108 	exec(cn, `DROP FUNCTION IF EXISTS hello`);
109 	exec(cn, `
110 		CREATE FUNCTION hello (s CHAR(20))
111 		RETURNS CHAR(50) DETERMINISTIC
112 		RETURN CONCAT('Hello ',s,'!')
113 	`);
114 
115 	auto preparedHello = prepareFunction(cn, "hello", 1);
116 	preparedHello.setArgs("World");
117 	auto rs = cn.query(preparedHello).array;
118 	assert(rs.length == 1);
119 	assert(rs[0][0] == "Hello World!");
120 }
121 
122 /++
123 Convenience function to create a prepared statement which calls a stored procedure.
124 
125 OUT parameters are currently not supported. It should generally be
126 possible with MySQL to present them as a result set.
127 
128 Be careful that your `numArgs` is correct. If it isn't, you may get a
129 `mysql.exceptions.MYX` with a very unclear error message.
130 
131 Throws: `mysql.exceptions.MYX` if the server has a problem.
132 
133 Params:
134 	name = The name of the stored procedure.
135 	numArgs = The number of arguments the stored procedure takes.
136 
137 +/
138 Prepared prepareProcedure(Connection conn, string name, int numArgs)
139 {
140 	auto sql = "call " ~ name ~ preparedPlaceholderArgs(numArgs);
141 	return prepare(conn, sql);
142 }
143 
144 ///
145 @("prepareProcedure")
146 debug(MYSQLN_TESTS)
147 unittest
148 {
149 	import mysql.test.common;
150 	import mysql.test.integration;
151 	mixin(scopedCn);
152 	initBaseTestTables(cn);
153 
154 	exec(cn, `DROP PROCEDURE IF EXISTS insert2`);
155 	exec(cn, `
156 		CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50))
157 		BEGIN
158 			INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2);
159 		END
160 	`);
161 
162 	auto preparedInsert2 = prepareProcedure(cn, "insert2", 2);
163 	preparedInsert2.setArgs(2001, "inserted string 1");
164 	cn.exec(preparedInsert2);
165 
166 	auto rs = query(cn, "SELECT stringcol FROM basetest WHERE intcol=2001").array;
167 	assert(rs.length == 1);
168 	assert(rs[0][0] == "inserted string 1");
169 }
170 
171 private string preparedPlaceholderArgs(int numArgs)
172 {
173 	auto sql = "(";
174 	bool comma = false;
175 	foreach(i; 0..numArgs)
176 	{
177 		if (comma)
178 			sql ~= ",?";
179 		else
180 		{
181 			sql ~= "?";
182 			comma = true;
183 		}
184 	}
185 	sql ~= ")";
186 
187 	return sql;
188 }
189 
190 @("preparedPlaceholderArgs")
191 debug(MYSQLN_TESTS)
192 unittest
193 {
194 	assert(preparedPlaceholderArgs(3) == "(?,?,?)");
195 	assert(preparedPlaceholderArgs(2) == "(?,?)");
196 	assert(preparedPlaceholderArgs(1) == "(?)");
197 	assert(preparedPlaceholderArgs(0) == "()");
198 }
199 
200 /// Per-connection info from the server about a registered prepared statement.
201 package struct PreparedServerInfo
202 {
203 	/// Server's identifier for this prepared statement.
204 	/// Apperently, this is never 0 if it's been registered,
205 	/// although mysql-native no longer relies on that.
206 	uint statementId;
207 
208 	ushort psWarnings;
209 
210 	/// Number of parameters this statement takes.
211 	/// 
212 	/// This will be the same on all connections, but it's returned
213 	/// by the server upon registration, so it's stored here.
214 	ushort numParams;
215 
216 	/// Prepared statement headers
217 	///
218 	/// This will be the same on all connections, but it's returned
219 	/// by the server upon registration, so it's stored here.
220 	PreparedStmtHeaders headers;
221 	
222 	/// Not actually from the server. Connection uses this to keep track
223 	/// of statements that should be treated as having been released.
224 	bool queuedForRelease = false;
225 }
226 
227 /++
228 This is a wrapper over `mysql.prepared.Prepared`, provided ONLY as a
229 temporary aid in upgrading to mysql-native v2.0.0 and its
230 new connection-independent model of prepared statements. See the
231 $(LINK2 https://github.com/mysql-d/mysql-native/blob/master/MIGRATING_TO_V2.md, migration guide)
232 for more info.
233 
234 In most cases, this layer shouldn't even be needed. But if you have many
235 lines of code making calls to exec/query the same prepared statement,
236 then this may be helpful.
237 
238 To use this temporary compatability layer, change instances of:
239 
240 ---
241 auto stmt = conn.prepare(...);
242 ---
243 
244 to this:
245 
246 ---
247 auto stmt = conn.prepareBackwardCompat(...);
248 ---
249 
250 And then your prepared statement should work as before.
251 
252 BUT DO NOT LEAVE IT LIKE THIS! Ultimately, you should update
253 your prepared statement code to the mysql-native v2.0.0 API, by changing
254 instances of:
255 
256 ---
257 stmt.exec()
258 stmt.query()
259 stmt.queryRow()
260 stmt.queryRowTuple(outputArgs...)
261 stmt.queryValue()
262 ---
263 
264 to this:
265 
266 ---
267 conn.exec(stmt)
268 conn.query(stmt)
269 conn.queryRow(stmt)
270 conn.queryRowTuple(stmt, outputArgs...)
271 conn.queryValue(stmt)
272 ---
273 
274 Both of the above syntaxes can be used with a `BackwardCompatPrepared`
275 (the `Connection` passed directly to `mysql.commands.exec`/`mysql.commands.query`
276 will override the one embedded associated with your `BackwardCompatPrepared`).
277 
278 Once all of your code is updated, you can change `prepareBackwardCompat`
279 back to `prepare` again, and your upgrade will be complete.
280 +/
281 struct BackwardCompatPrepared
282 {
283 	import std.variant;
284 	
285 	private Connection _conn;
286 	Prepared _prepared;
287 
288 	/// Access underlying `Prepared`
289 	@property Prepared prepared() { return _prepared; }
290 
291 	alias _prepared this;
292 
293 	/++
294 	This function is provided ONLY as a temporary aid in upgrading to mysql-native v2.0.0.
295 	
296 	See `BackwardCompatPrepared` for more info.
297 	+/
298 	deprecated("Change 'preparedStmt.exec()' to 'conn.exec(preparedStmt)'")
299 	ulong exec()
300 	{
301 		return .exec(_conn, _prepared);
302 	}
303 
304 	///ditto
305 	deprecated("Change 'preparedStmt.query()' to 'conn.query(preparedStmt)'")
306 	ResultRange query()
307 	{
308 		return .query(_conn, _prepared);
309 	}
310 
311 	///ditto
312 	deprecated("Change 'preparedStmt.queryRow()' to 'conn.queryRow(preparedStmt)'")
313 	Nullable!Row queryRow()
314 	{
315 		return .queryRow(_conn, _prepared);
316 	}
317 
318 	///ditto
319 	deprecated("Change 'preparedStmt.queryRowTuple(outArgs...)' to 'conn.queryRowTuple(preparedStmt, outArgs...)'")
320 	void queryRowTuple(T...)(ref T args) if(T.length == 0 || !is(T[0] : Connection))
321 	{
322 		return .queryRowTuple(_conn, _prepared, args);
323 	}
324 
325 	///ditto
326 	deprecated("Change 'preparedStmt.queryValue()' to 'conn.queryValue(preparedStmt)'")
327 	Nullable!Variant queryValue()
328 	{
329 		return .queryValue(_conn, _prepared);
330 	}
331 }
332 
333 /++
334 A class representing a database connection.
335 
336 If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of
337 creating a new Connection directly. That will provide certain benefits,
338 such as reusing old connections and automatic cleanup (no need to close
339 the connection when done).
340 
341 ------------------
342 // Suggested usage:
343 
344 {
345 	auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb");
346 	scope(exit) con.close();
347 
348 	// Use the connection
349 	...
350 }
351 ------------------
352 +/
353 //TODO: All low-level commms should be moved into the mysql.protocol package.
354 class Connection
355 {
356 /+
357 The Connection is responsible for handshaking with the server to establish
358 authentication. It then passes client preferences to the server, and
359 subsequently is the channel for all command packets that are sent, and all
360 response packets received.
361 
362 Uncompressed packets consist of a 4 byte header - 3 bytes of length, and one
363 byte as a packet number. Connection deals with the headers and ensures that
364 packet numbers are sequential.
365 
366 The initial packet is sent by the server - essentially a 'hello' packet
367 inviting login. That packet has a sequence number of zero. That sequence
368 number is the incremented by client and server packets through the handshake
369 sequence.
370 
371 After login all further sequences are initialized by the client sending a
372 command packet with a zero sequence number, to which the server replies with
373 zero or more packets with sequential sequence numbers.
374 +/
375 package:
376 	enum OpenState
377 	{
378 		/// We have not yet connected to the server, or have sent QUIT to the
379 		/// server and closed the connection
380 		notConnected,
381 		/// We have connected to the server and parsed the greeting, but not
382 		/// yet authenticated
383 		connected,
384 		/// We have successfully authenticated against the server, and need to
385 		/// send QUIT to the server when closing the connection
386 		authenticated
387 	}
388 	OpenState   _open;
389 	MySQLSocket _socket;
390 
391 	SvrCapFlags _sCaps, _cCaps;
392 	uint    _sThread;
393 	ushort  _serverStatus;
394 	ubyte   _sCharSet, _protocol;
395 	string  _serverVersion;
396 
397 	string _host, _user, _pwd, _db;
398 	ushort _port;
399 
400 	MySQLSocketType _socketType;
401 
402 	OpenSocketCallbackPhobos _openSocketPhobos;
403 	OpenSocketCallbackVibeD  _openSocketVibeD;
404 
405 	ulong _insertID;
406 
407 	// This gets incremented every time a command is issued or results are purged,
408 	// so a ResultRange can tell whether it's been invalidated.
409 	ulong _lastCommandID;
410 
411 	// Whether there are rows, headers or bimary data waiting to be retreived.
412 	// MySQL protocol doesn't permit performing any other action until all
413 	// such data is read.
414 	bool _rowsPending, _headersPending, _binaryPending;
415 
416 	// Field count of last performed command.
417 	//TODO: Does Connection need to store this?
418 	ushort _fieldCount;
419 
420 	// ResultSetHeaders of last performed command.
421 	//TODO: Does Connection need to store this? Is this even used?
422 	ResultSetHeaders _rsh;
423 
424 	// This tiny thing here is pretty critical. Pay great attention to it's maintenance, otherwise
425 	// you'll get the dreaded "packet out of order" message. It, and the socket connection are
426 	// the reason why most other objects require a connection object for their construction.
427 	ubyte _cpn; /// Packet Number in packet header. Serial number to ensure correct
428 				/// ordering. First packet should have 0
429 	@property ubyte pktNumber()   { return _cpn; }
430 	void bumpPacket()       { _cpn++; }
431 	void resetPacket()      { _cpn = 0; }
432 
433 	version(Have_vibe_d_core) {} else
434 	pure const nothrow invariant()
435 	{
436 		assert(_socketType != MySQLSocketType.vibed);
437 	}
438 
439 	static PlainPhobosSocket defaultOpenSocketPhobos(string host, ushort port)
440 	{
441 		auto s = new PlainPhobosSocket();
442 		s.connect(new InternetAddress(host, port));
443 		return s;
444 	}
445 
446 	static PlainVibeDSocket defaultOpenSocketVibeD(string host, ushort port)
447 	{
448 		version(Have_vibe_d_core)
449 			return vibe.core.net.connectTCP(host, port);
450 		else
451 			assert(0);
452 	}
453 
454 	void initConnection()
455 	{
456 		kill(); // Ensure internal state gets reset
457 
458 		resetPacket();
459 		final switch(_socketType)
460 		{
461 			case MySQLSocketType.phobos:
462 				_socket = new MySQLSocketPhobos(_openSocketPhobos(_host, _port));
463 				break;
464 
465 			case MySQLSocketType.vibed:
466 				version(Have_vibe_d_core) {
467 					_socket = new MySQLSocketVibeD(_openSocketVibeD(_host, _port));
468 					break;
469 				} else assert(0, "Unsupported socket type. Need version Have_vibe_d_core.");
470 		}
471 	}
472 
473 	SvrCapFlags _clientCapabilities;
474 
475 	void connect(SvrCapFlags clientCapabilities)
476 	out
477 	{
478 		assert(_open == OpenState.authenticated);
479 	}
480 	body
481 	{
482 		initConnection();
483 		auto greeting = this.parseGreeting();
484 		_open = OpenState.connected;
485 
486 		_clientCapabilities = clientCapabilities;
487 		_cCaps = setClientFlags(_sCaps, clientCapabilities);
488 		this.authenticate(greeting);
489 	}
490 	
491 	/++
492 	Forcefully close the socket without sending the quit command.
493 	
494 	Also resets internal state regardless of whether the connection is open or not.
495 	
496 	Needed in case an error leaves communatations in an undefined or non-recoverable state.
497 	+/
498 	void kill()
499 	{
500 		if(_socket && _socket.connected)
501 			_socket.close();
502 		_open = OpenState.notConnected;
503 		// any pending data is gone. Any statements to release will be released
504 		// on the server automatically.
505 		_headersPending = _rowsPending = _binaryPending = false;
506 
507 		preparedRegistrations.clear();
508 
509 		_lastCommandID++; // Invalidate result sets
510 	}
511 	
512 	/// Called whenever mysql-native needs to send a command to the server
513 	/// and be sure there aren't any pending results (which would prevent
514 	/// a new command from being sent).
515 	void autoPurge()
516 	{
517 		// This is called every time a command is sent,
518 		// so detect & prevent infinite recursion.
519 		static bool isAutoPurging = false;
520 
521 		if(isAutoPurging)
522 			return;
523 			
524 		isAutoPurging = true;
525 		scope(exit) isAutoPurging = false;
526 
527 		try
528 		{
529 			purgeResult();
530 			releaseQueued();
531 		}
532 		catch(Exception e)
533 		{
534 			// Likely the connection was closed, so reset any state (and force-close if needed).
535 			// Don't treat this as a real error, because everything will be reset when we
536 			// reconnect.
537 			kill();
538 		}
539 	}
540 
541 	/// Lookup per-connection prepared statement info by SQL
542 	private PreparedRegistrations!PreparedServerInfo preparedRegistrations;
543 
544 	/// Releases all prepared statements that are queued for release.
545 	void releaseQueued()
546 	{
547 		foreach(sql, info; preparedRegistrations.directLookup)
548 		if(info.queuedForRelease)
549 		{
550 			immediateReleasePrepared(this, info.statementId);
551 			preparedRegistrations.directLookup.remove(sql);
552 		}
553 	}
554 
555 	/// Returns null if not found
556 	Nullable!PreparedServerInfo getPreparedServerInfo(const(char[]) sql) pure nothrow
557 	{
558 		return preparedRegistrations[sql];
559 	}
560 
561 	/// If already registered, simply returns the cached `PreparedServerInfo`.
562 	PreparedServerInfo registerIfNeeded(const(char[]) sql)
563 	{
564 		return preparedRegistrations.registerIfNeeded(sql, sql => performRegister(this, sql));
565 	}
566 
567 public:
568 
569 	/++
570 	Construct opened connection.
571 
572 	Throws `mysql.exceptions.MYX` upon failure to connect.
573 	
574 	If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of
575 	creating a new Connection directly. That will provide certain benefits,
576 	such as reusing old connections and automatic cleanup (no need to close
577 	the connection when done).
578 
579 	------------------
580 	// Suggested usage:
581 
582 	{
583 	    auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb");
584 	    scope(exit) con.close();
585 
586 	    // Use the connection
587 	    ...
588 	}
589 	------------------
590 
591 	Params:
592 		cs = A connection string of the form "host=localhost;user=user;pwd=password;db=mysqld"
593 			(TODO: The connection string needs work to allow for semicolons in its parts!)
594 		socketType = Whether to use a Phobos or Vibe.d socket. Default is Phobos,
595 			unless compiled with `-version=Have_vibe_d_core` (set automatically
596 			if using $(LINK2 http://code.dlang.org/getting_started, DUB)).
597 		openSocket = Optional callback which should return a newly-opened Phobos
598 			or Vibe.d TCP socket. This allows custom sockets to be used,
599 			subclassed from Phobos's or Vibe.d's sockets.
600 		host = An IP address in numeric dotted form, or as a host  name.
601 		user = The user name to authenticate.
602 		password = User's password.
603 		db = Desired initial database.
604 		capFlags = The set of flag bits from the server's capabilities that the client requires
605 	+/
606 	//After the connection is created, and the initial invitation is received from the server
607 	//client preferences can be set, and authentication can then be attempted.
608 	this(string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
609 	{
610 		version(Have_vibe_d_core)
611 			enum defaultSocketType = MySQLSocketType.vibed;
612 		else
613 			enum defaultSocketType = MySQLSocketType.phobos;
614 
615 		this(defaultSocketType, host, user, pwd, db, port, capFlags);
616 	}
617 
618 	///ditto
619 	this(MySQLSocketType socketType, string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
620 	{
621 		version(Have_vibe_d_core) {} else
622 			enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_d_core");
623 
624 		this(socketType, &defaultOpenSocketPhobos, &defaultOpenSocketVibeD,
625 			host, user, pwd, db, port, capFlags);
626 	}
627 
628 	///ditto
629 	this(OpenSocketCallbackPhobos openSocket,
630 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
631 	{
632 		this(MySQLSocketType.phobos, openSocket, null, host, user, pwd, db, port, capFlags);
633 	}
634 
635 	version(Have_vibe_d_core)
636 	///ditto
637 	this(OpenSocketCallbackVibeD openSocket,
638 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
639 	{
640 		this(MySQLSocketType.vibed, null, openSocket, host, user, pwd, db, port, capFlags);
641 	}
642 
643 	///ditto
644 	private this(MySQLSocketType socketType,
645 		OpenSocketCallbackPhobos openSocketPhobos, OpenSocketCallbackVibeD openSocketVibeD,
646 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
647 	in
648 	{
649 		final switch(socketType)
650 		{
651 			case MySQLSocketType.phobos: assert(openSocketPhobos !is null); break;
652 			case MySQLSocketType.vibed:  assert(openSocketVibeD  !is null); break;
653 		}
654 	}
655 	body
656 	{
657 		enforce!MYX(capFlags & SvrCapFlags.PROTOCOL41, "This client only supports protocol v4.1");
658 		enforce!MYX(capFlags & SvrCapFlags.SECURE_CONNECTION, "This client only supports protocol v4.1 connection");
659 		version(Have_vibe_d_core) {} else
660 			enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_d_core");
661 
662 		_socketType = socketType;
663 		_host = host;
664 		_user = user;
665 		_pwd = pwd;
666 		_db = db;
667 		_port = port;
668 
669 		_openSocketPhobos = openSocketPhobos;
670 		_openSocketVibeD  = openSocketVibeD;
671 
672 		connect(capFlags);
673 	}
674 
675 	///ditto
676 	//After the connection is created, and the initial invitation is received from the server
677 	//client preferences can be set, and authentication can then be attempted.
678 	this(string cs, SvrCapFlags capFlags = defaultClientFlags)
679 	{
680 		string[] a = parseConnectionString(cs);
681 		this(a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
682 	}
683 
684 	///ditto
685 	this(MySQLSocketType socketType, string cs, SvrCapFlags capFlags = defaultClientFlags)
686 	{
687 		string[] a = parseConnectionString(cs);
688 		this(socketType, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
689 	}
690 
691 	///ditto
692 	this(OpenSocketCallbackPhobos openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags)
693 	{
694 		string[] a = parseConnectionString(cs);
695 		this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
696 	}
697 
698 	version(Have_vibe_d_core)
699 	///ditto
700 	this(OpenSocketCallbackVibeD openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags)
701 	{
702 		string[] a = parseConnectionString(cs);
703 		this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
704 	}
705 
706 	/++
707 	Check whether this `Connection` is still connected to the server, or if
708 	the connection has been closed.
709 	+/
710 	@property bool closed()
711 	{
712 		return _open == OpenState.notConnected || !_socket.connected;
713 	}
714 
715 	/++
716 	Explicitly close the connection.
717 	
718 	Idiomatic use as follows is suggested:
719 	------------------
720 	{
721 	    auto con = new Connection("localhost:user:password:mysqld");
722 	    scope(exit) con.close();
723 	    // Use the connection
724 	    ...
725 	}
726 	------------------
727 	+/
728 	void close()
729 	{
730 		// This is a two-stage process. First tell the server we are quitting this
731 		// connection, and then close the socket.
732 
733 		if (_open == OpenState.authenticated && _socket.connected)
734 			quit();
735 
736 		if (_open == OpenState.connected)
737 			kill();
738 		resetPacket();
739 	}
740 
741 	/++
742 	Reconnects to the server using the same connection settings originally
743 	used to create the `Connection`.
744 
745 	Optionally takes a `mysql.protocol.constants.SvrCapFlags`, allowing you to
746 	reconnect using a different set of server capability flags.
747 
748 	Normally, if the connection is already open, this will do nothing. However,
749 	if you request a different set of `mysql.protocol.constants.SvrCapFlags`
750 	then was originally used to create the `Connection`, the connection will
751 	be closed and then reconnected using the new `mysql.protocol.constants.SvrCapFlags`.
752 	+/
753 	void reconnect()
754 	{
755 		reconnect(_clientCapabilities);
756 	}
757 
758 	///ditto
759 	void reconnect(SvrCapFlags clientCapabilities)
760 	{
761 		bool sameCaps = clientCapabilities == _clientCapabilities;
762 		if(!closed)
763 		{
764 			// Same caps as before?
765 			if(clientCapabilities == _clientCapabilities)
766 				return; // Nothing to do, just keep current connection
767 
768 			close();
769 		}
770 
771 		connect(clientCapabilities);
772 	}
773 
774 	// This also serves as a regression test for #167:
775 	// ResultRange doesn't get invalidated upon reconnect
776 	@("reconnect")
777 	debug(MYSQLN_TESTS)
778 	unittest
779 	{
780 		import std.variant;
781 		mixin(scopedCn);
782 		cn.exec("DROP TABLE IF EXISTS `reconnect`");
783 		cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
784 		cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)");
785 
786 		enum sql = "SELECT a FROM `reconnect`";
787 
788 		// Sanity check
789 		auto rows = cn.query(sql).array;
790 		assert(rows[0][0] == 1);
791 		assert(rows[1][0] == 2);
792 		assert(rows[2][0] == 3);
793 
794 		// Ensure reconnect keeps the same connection when it's supposed to
795 		auto range = cn.query(sql);
796 		assert(range.front[0] == 1);
797 		cn.reconnect();
798 		assert(!cn.closed); // Is open?
799 		assert(range.isValid); // Still valid?
800 		range.popFront();
801 		assert(range.front[0] == 2);
802 
803 		// Ensure reconnect reconnects when it's supposed to
804 		range = cn.query(sql);
805 		assert(range.front[0] == 1);
806 		cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities
807 		cn.reconnect(~cn._clientCapabilities);
808 		assert(!cn.closed); // Is open?
809 		assert(!range.isValid); // Was invalidated?
810 		cn.query(sql).array; // Connection still works?
811 
812 		// Try manually reconnecting
813 		range = cn.query(sql);
814 		assert(range.front[0] == 1);
815 		cn.connect(cn._clientCapabilities);
816 		assert(!cn.closed); // Is open?
817 		assert(!range.isValid); // Was invalidated?
818 		cn.query(sql).array; // Connection still works?
819 
820 		// Try manually closing and connecting
821 		range = cn.query(sql);
822 		assert(range.front[0] == 1);
823 		cn.close();
824 		assert(cn.closed); // Is closed?
825 		assert(!range.isValid); // Was invalidated?
826 		cn.connect(cn._clientCapabilities);
827 		assert(!cn.closed); // Is open?
828 		assert(!range.isValid); // Was invalidated?
829 		cn.query(sql).array; // Connection still works?
830 
831 		// Auto-reconnect upon a command
832 		cn.close();
833 		assert(cn.closed);
834 		range = cn.query(sql);
835 		assert(!cn.closed);
836 		assert(range.front[0] == 1);
837 	}
838 	
839 	private void quit()
840 	in
841 	{
842 		assert(_open == OpenState.authenticated);
843 	}
844 	body
845 	{
846 		this.sendCmd(CommandType.QUIT, []);
847 		// No response is sent for a quit packet
848 		_open = OpenState.connected;
849 	}
850 
851 	/++
852 	Parses a connection string of the form
853 	`"host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb"`
854 
855 	Port is optional and defaults to 3306.
856 
857 	Whitespace surrounding any name or value is automatically stripped.
858 
859 	Returns a five-element array of strings in this order:
860 	$(UL
861 	$(LI [0]: host)
862 	$(LI [1]: user)
863 	$(LI [2]: pwd)
864 	$(LI [3]: db)
865 	$(LI [4]: port)
866 	)
867 	
868 	(TODO: The connection string needs work to allow for semicolons in its parts!)
869 	+/
870 	//TODO: Replace the return value with a proper struct.
871 	static string[] parseConnectionString(string cs)
872 	{
873 		string[] rv;
874 		rv.length = 5;
875 		rv[4] = "3306"; // Default port
876 		string[] a = split(cs, ";");
877 		foreach (s; a)
878 		{
879 			string[] a2 = split(s, "=");
880 			enforce!MYX(a2.length == 2, "Bad connection string: " ~ cs);
881 			string name = strip(a2[0]);
882 			string val = strip(a2[1]);
883 			switch (name)
884 			{
885 				case "host":
886 					rv[0] = val;
887 					break;
888 				case "user":
889 					rv[1] = val;
890 					break;
891 				case "pwd":
892 					rv[2] = val;
893 					break;
894 				case "db":
895 					rv[3] = val;
896 					break;
897 				case "port":
898 					rv[4] = val;
899 					break;
900 				default:
901 					throw new MYX("Bad connection string: " ~ cs, __FILE__, __LINE__);
902 			}
903 		}
904 		return rv;
905 	}
906 
907 	/++
908 	Select a current database.
909 	
910 	Throws `mysql.exceptions.MYX` upon failure.
911 
912 	Params: dbName = Name of the requested database
913 	+/
914 	void selectDB(string dbName)
915 	{
916 		this.sendCmd(CommandType.INIT_DB, dbName);
917 		this.getCmdResponse();
918 		_db = dbName;
919 	}
920 
921 	/++
922 	Check the server status.
923 	
924 	Throws `mysql.exceptions.MYX` upon failure.
925 
926 	Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined
927 	+/
928 	OKErrorPacket pingServer()
929 	{
930 		this.sendCmd(CommandType.PING, []);
931 		return this.getCmdResponse();
932 	}
933 
934 	/++
935 	Refresh some feature(s) of the server.
936 	
937 	Throws `mysql.exceptions.MYX` upon failure.
938 
939 	Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined
940 	+/
941 	OKErrorPacket refreshServer(RefreshFlags flags)
942 	{
943 		this.sendCmd(CommandType.REFRESH, [flags]);
944 		return this.getCmdResponse();
945 	}
946 
947 	/++
948 	Flush any outstanding result set elements.
949 	
950 	When the server responds to a command that produces a result set, it
951 	queues the whole set of corresponding packets over the current connection.
952 	Before that `Connection` can embark on any new command, it must receive
953 	all of those packets and junk them.
954 	
955 	As of v1.1.4, this is done automatically as needed. But you can still
956 	call this manually to force a purge to occur when you want.
957 
958 	See_Also: $(LINK http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/)
959 	+/
960 	ulong purgeResult()
961 	{
962 		return mysql.protocol.comms.purgeResult(this);
963 	}
964 
965 	/++
966 	Get a textual report on the server status.
967 	
968 	(COM_STATISTICS)
969 	+/
970 	string serverStats()
971 	{
972 		return mysql.protocol.comms.serverStats(this);
973 	}
974 
975 	/++
976 	Enable multiple statement commands.
977 	
978 	This can be used later if this feature was not requested in the client capability flags.
979 	
980 	Warning: This functionality is currently untested.
981 	
982 	Params: on = Boolean value to turn the capability on or off.
983 	+/
984 	//TODO: Need to test this
985 	void enableMultiStatements(bool on)
986 	{
987 		mysql.protocol.comms.enableMultiStatements(this, on);
988 	}
989 
990 	/// Return the in-force protocol number.
991 	@property ubyte protocol() pure const nothrow { return _protocol; }
992 	/// Server version
993 	@property string serverVersion() pure const nothrow { return _serverVersion; }
994 	/// Server capability flags
995 	@property uint serverCapabilities() pure const nothrow { return _sCaps; }
996 	/// Server status
997 	@property ushort serverStatus() pure const nothrow { return _serverStatus; }
998 	/// Current character set
999 	@property ubyte charSet() pure const nothrow { return _sCharSet; }
1000 	/// Current database
1001 	@property string currentDB() pure const nothrow { return _db; }
1002 	/// Socket type being used, Phobos or Vibe.d
1003 	@property MySQLSocketType socketType() pure const nothrow { return _socketType; }
1004 
1005 	/// After a command that inserted a row into a table with an auto-increment
1006 	/// ID column, this method allows you to retrieve the last insert ID.
1007 	@property ulong lastInsertID() pure const nothrow { return _insertID; }
1008 
1009 	/// This gets incremented every time a command is issued or results are purged,
1010 	/// so a `mysql.result.ResultRange` can tell whether it's been invalidated.
1011 	@property ulong lastCommandID() pure const nothrow { return _lastCommandID; }
1012 
1013 	/// Gets whether rows are pending.
1014 	///
1015 	/// Note, you may want `hasPending` instead.
1016 	@property bool rowsPending() pure const nothrow { return _rowsPending; }
1017 
1018 	/// Gets whether anything (rows, headers or binary) is pending.
1019 	/// New commands cannot be sent on a connection while anything is pending
1020 	/// (the pending data will automatically be purged.)
1021 	@property bool hasPending() pure const nothrow
1022 	{
1023 		return _rowsPending || _headersPending || _binaryPending;
1024 	}
1025 
1026 	/// Gets the result header's field descriptions.
1027 	@property FieldDescription[] resultFieldDescriptions() pure { return _rsh.fieldDescriptions; }
1028 
1029 	/++
1030 	Manually register a prepared statement on this connection.
1031 	
1032 	Does nothing if statement is already registered on this connection.
1033 	
1034 	Calling this is not strictly necessary, as the prepared statement will
1035 	automatically be registered upon its first use on any `Connection`.
1036 	This is provided for those who prefer eager registration over lazy
1037 	for performance reasons.
1038 	+/
1039 	void register(Prepared prepared)
1040 	{
1041 		register(prepared.sql);
1042 	}
1043 
1044 	///ditto
1045 	void register(const(char[]) sql)
1046 	{
1047 		registerIfNeeded(sql);
1048 	}
1049 
1050 	/++
1051 	Manually release a prepared statement on this connection.
1052 	
1053 	This method tells the server that it can dispose of the information it
1054 	holds about the current prepared statement.
1055 	
1056 	Calling this is not strictly necessary. The server considers prepared
1057 	statements to be per-connection, so they'll go away when the connection
1058 	closes anyway. This is provided in case direct control is actually needed.
1059 
1060 	If you choose to use a reference counted struct to call this automatically,
1061 	be aware that embedding reference counted structs inside garbage collectible
1062 	heap objects is dangerous and should be avoided, as it can lead to various
1063 	hidden problems, from crashes to race conditions. (See the discussion at issue
1064 	$(LINK2 https://github.com/mysql-d/mysql-native/issues/159, #159)
1065 	for details.) Instead, it may be better to simply avoid trying to manage
1066 	their release at all, as it's not usually necessary. Or to periodically
1067 	release all prepared statements, and simply allow mysql-native to
1068 	automatically re-register them upon their next use.
1069 	
1070 	Notes:
1071 	
1072 	In actuality, the server might not immediately be told to release the
1073 	statement (although `isRegistered` will still report `false`).
1074 	
1075 	This is because there could be a `mysql.result.ResultRange` with results
1076 	still pending for retrieval, and the protocol doesn't allow sending commands
1077 	(such as "release a prepared statement") to the server while data is pending.
1078 	Therefore, this function may instead queue the statement to be released
1079 	when it is safe to do so: Either the next time a result set is purged or
1080 	the next time a command (such as `mysql.commands.query` or
1081 	`mysql.commands.exec`) is performed (because such commands automatically
1082 	purge any pending results).
1083 	
1084 	This function does NOT auto-purge because, if this is ever called from
1085 	automatic resource management cleanup (refcounting, RAII, etc), that
1086 	would create ugly situations where hidden, implicit behavior triggers
1087 	an unexpected auto-purge.
1088 	+/
1089 	void release(Prepared prepared)
1090 	{
1091 		release(prepared.sql);
1092 	}
1093 	
1094 	///ditto
1095 	void release(const(char[]) sql)
1096 	{
1097 		//TODO: Don't queue it if nothing is pending. Just do it immediately.
1098 		//      But need to be certain both situations are unittested.
1099 		preparedRegistrations.queueForRelease(sql);
1100 	}
1101 	
1102 	/++
1103 	Manually release all prepared statements on this connection.
1104 	
1105 	While minimal, every prepared statement registered on a connection does
1106 	use up a small amount of resources in both mysql-native and on the server.
1107 	Additionally, servers can be configured
1108 	$(LINK2 https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_prepared_stmt_count,
1109 	to limit the number of prepared statements)
1110 	allowed on a connection at one time (the default, however
1111 	is quite high). Note also, that certain overloads of `mysql.commands.exec`,
1112 	`mysql.commands.query`, etc. register prepared statements behind-the-scenes
1113 	which are cached for quick re-use later.
1114 	
1115 	Therefore, it may occasionally be useful to clear out all prepared
1116 	statements on a connection, together with all resources used by them (or
1117 	at least leave the resources ready for garbage-collection). This function
1118 	does just that.
1119 	
1120 	Note that this is ALWAYS COMPLETELY SAFE to call, even if you still have
1121 	live prepared statements you intend to use again. This is safe because
1122 	mysql-native will automatically register or re-register prepared statements
1123 	as-needed.
1124 
1125 	Notes:
1126 	
1127 	In actuality, the prepared statements might not be immediately released
1128 	(although `isRegistered` will still report `false` for them).
1129 	
1130 	This is because there could be a `mysql.result.ResultRange` with results
1131 	still pending for retrieval, and the protocol doesn't allow sending commands
1132 	(such as "release a prepared statement") to the server while data is pending.
1133 	Therefore, this function may instead queue the statement to be released
1134 	when it is safe to do so: Either the next time a result set is purged or
1135 	the next time a command (such as `mysql.commands.query` or
1136 	`mysql.commands.exec`) is performed (because such commands automatically
1137 	purge any pending results).
1138 	
1139 	This function does NOT auto-purge because, if this is ever called from
1140 	automatic resource management cleanup (refcounting, RAII, etc), that
1141 	would create ugly situations where hidden, implicit behavior triggers
1142 	an unexpected auto-purge.
1143 	+/
1144 	void releaseAll()
1145 	{
1146 		preparedRegistrations.queueAllForRelease();
1147 	}
1148 
1149 	@("releaseAll")
1150 	debug(MYSQLN_TESTS)
1151 	unittest
1152 	{
1153 		mixin(scopedCn);
1154 		
1155 		cn.exec("DROP TABLE IF EXISTS `releaseAll`");
1156 		cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1157 
1158 		auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`");
1159 		auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)");
1160 		assert(cn.isRegistered(preparedSelect));
1161 		assert(cn.isRegistered(preparedInsert));
1162 
1163 		cn.releaseAll();
1164 		assert(!cn.isRegistered(preparedSelect));
1165 		assert(!cn.isRegistered(preparedInsert));
1166 		cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)");
1167 		assert(!cn.isRegistered(preparedSelect));
1168 		assert(!cn.isRegistered(preparedInsert));
1169 
1170 		cn.exec(preparedInsert);
1171 		cn.query(preparedSelect).array;
1172 		assert(cn.isRegistered(preparedSelect));
1173 		assert(cn.isRegistered(preparedInsert));
1174 
1175 	}
1176 
1177 	/// Is the given statement registered on this connection as a prepared statement?
1178 	bool isRegistered(Prepared prepared)
1179 	{
1180 		return isRegistered( prepared.sql );
1181 	}
1182 
1183 	///ditto
1184 	bool isRegistered(const(char[]) sql)
1185 	{
1186 		return isRegistered( preparedRegistrations[sql] );
1187 	}
1188 
1189 	///ditto
1190 	package bool isRegistered(Nullable!PreparedServerInfo info)
1191 	{
1192 		return !info.isNull && !info.queuedForRelease;
1193 	}
1194 }
1195 
1196 // Test register, release, isRegistered, and auto-register for prepared statements
1197 @("autoRegistration")
1198 debug(MYSQLN_TESTS)
1199 unittest
1200 {
1201 	import mysql.connection;
1202 	import mysql.test.common;
1203 	
1204 	Prepared preparedInsert;
1205 	Prepared preparedSelect;
1206 	immutable insertSQL = "INSERT INTO `autoRegistration` VALUES (1), (2)";
1207 	immutable selectSQL = "SELECT `val` FROM `autoRegistration`";
1208 	int queryTupleResult;
1209 	
1210 	{
1211 		mixin(scopedCn);
1212 		
1213 		// Setup
1214 		cn.exec("DROP TABLE IF EXISTS `autoRegistration`");
1215 		cn.exec("CREATE TABLE `autoRegistration` (
1216 			`val` INTEGER
1217 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1218 
1219 		// Initial register
1220 		preparedInsert = cn.prepare(insertSQL);
1221 		preparedSelect = cn.prepare(selectSQL);
1222 		
1223 		// Test basic register, release, isRegistered
1224 		assert(cn.isRegistered(preparedInsert));
1225 		assert(cn.isRegistered(preparedSelect));
1226 		cn.release(preparedInsert);
1227 		cn.release(preparedSelect);
1228 		assert(!cn.isRegistered(preparedInsert));
1229 		assert(!cn.isRegistered(preparedSelect));
1230 		
1231 		// Test manual re-register
1232 		cn.register(preparedInsert);
1233 		cn.register(preparedSelect);
1234 		assert(cn.isRegistered(preparedInsert));
1235 		assert(cn.isRegistered(preparedSelect));
1236 		
1237 		// Test double register
1238 		cn.register(preparedInsert);
1239 		cn.register(preparedSelect);
1240 		assert(cn.isRegistered(preparedInsert));
1241 		assert(cn.isRegistered(preparedSelect));
1242 
1243 		// Test double release
1244 		cn.release(preparedInsert);
1245 		cn.release(preparedSelect);
1246 		assert(!cn.isRegistered(preparedInsert));
1247 		assert(!cn.isRegistered(preparedSelect));
1248 		cn.release(preparedInsert);
1249 		cn.release(preparedSelect);
1250 		assert(!cn.isRegistered(preparedInsert));
1251 		assert(!cn.isRegistered(preparedSelect));
1252 	}
1253 
1254 	// Note that at this point, both prepared statements still exist,
1255 	// but are no longer registered on any connection. In fact, there
1256 	// are no open connections anymore.
1257 	
1258 	// Test auto-register: exec
1259 	{
1260 		mixin(scopedCn);
1261 	
1262 		assert(!cn.isRegistered(preparedInsert));
1263 		cn.exec(preparedInsert);
1264 		assert(cn.isRegistered(preparedInsert));
1265 	}
1266 	
1267 	// Test auto-register: query
1268 	{
1269 		mixin(scopedCn);
1270 	
1271 		assert(!cn.isRegistered(preparedSelect));
1272 		cn.query(preparedSelect).each();
1273 		assert(cn.isRegistered(preparedSelect));
1274 	}
1275 	
1276 	// Test auto-register: queryRow
1277 	{
1278 		mixin(scopedCn);
1279 	
1280 		assert(!cn.isRegistered(preparedSelect));
1281 		cn.queryRow(preparedSelect);
1282 		assert(cn.isRegistered(preparedSelect));
1283 	}
1284 	
1285 	// Test auto-register: queryRowTuple
1286 	{
1287 		mixin(scopedCn);
1288 	
1289 		assert(!cn.isRegistered(preparedSelect));
1290 		cn.queryRowTuple(preparedSelect, queryTupleResult);
1291 		assert(cn.isRegistered(preparedSelect));
1292 	}
1293 	
1294 	// Test auto-register: queryValue
1295 	{
1296 		mixin(scopedCn);
1297 	
1298 		assert(!cn.isRegistered(preparedSelect));
1299 		cn.queryValue(preparedSelect);
1300 		assert(cn.isRegistered(preparedSelect));
1301 	}
1302 }
1303 
1304 // An attempt to reproduce issue #81: Using mysql-native driver with no default database
1305 // I'm unable to actually reproduce the error, though.
1306 @("issue81")
1307 debug(MYSQLN_TESTS)
1308 unittest
1309 {
1310 	import mysql.escape;
1311 	mixin(scopedCn);
1312 	
1313 	cn.exec("DROP TABLE IF EXISTS `issue81`");
1314 	cn.exec("CREATE TABLE `issue81` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1315 	cn.exec("INSERT INTO `issue81` (a) VALUES (1)");
1316 
1317 	auto cn2 = new Connection(text("host=", cn._host, ";port=", cn._port, ";user=", cn._user, ";pwd=", cn._pwd));
1318 	scope(exit) cn2.close();
1319 	
1320 	cn2.query("SELECT * FROM `"~mysqlEscape(cn._db).text~"`.`issue81`");
1321 }
1322 
1323 // Regression test for Issue #154:
1324 // autoPurge can throw an exception if the socket was closed without purging
1325 //
1326 // This simulates a disconnect by closing the socket underneath the Connection
1327 // object itself.
1328 @("dropConnection")
1329 debug(MYSQLN_TESTS)
1330 unittest
1331 {
1332 	mixin(scopedCn);
1333 
1334 	cn.exec("DROP TABLE IF EXISTS `dropConnection`");
1335 	cn.exec("CREATE TABLE `dropConnection` (
1336 		`val` INTEGER
1337 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1338 	cn.exec("INSERT INTO `dropConnection` VALUES (1), (2), (3)");
1339 	import mysql.prepared;
1340 	{
1341 		auto prep = cn.prepare("SELECT * FROM `dropConnection`");
1342 		cn.query(prep);
1343 	}
1344 	// close the socket forcibly
1345 	cn._socket.close();
1346 	// this should still work (it should reconnect).
1347 	cn.exec("DROP TABLE `dropConnection`");
1348 }
1349 
1350 /+
1351 Test Prepared's ability to be safely refcount-released during a GC cycle
1352 (ie, `Connection.release` must not allocate GC memory).
1353 
1354 Currently disabled because it's not guaranteed to always work
1355 (and apparently, cannot be made to work?)
1356 For relevant discussion, see issue #159:
1357 https://github.com/mysql-d/mysql-native/issues/159
1358 +/
1359 version(none)
1360 debug(MYSQLN_TESTS)
1361 {
1362 	/// Proof-of-concept ref-counted Prepared wrapper, just for testing,
1363 	/// not really intended for actual use.
1364 	private struct RCPreparedPayload
1365 	{
1366 		Prepared prepared;
1367 		Connection conn; // Connection to be released from
1368 
1369 		alias prepared this;
1370 
1371 		@disable this(this); // not copyable
1372 		~this()
1373 		{
1374 			// There are a couple calls to this dtor where `conn` happens to be null.
1375 			if(conn is null)
1376 				return;
1377 
1378 			assert(conn.isRegistered(prepared));
1379 			conn.release(prepared);
1380 		}
1381 	}
1382 	///ditto
1383 	alias RCPrepared = RefCounted!(RCPreparedPayload, RefCountedAutoInitialize.no);
1384 	///ditto
1385 	private RCPrepared rcPrepare(Connection conn, const(char[]) sql)
1386 	{
1387 		import std.algorithm.mutation : move;
1388 
1389 		auto prepared = conn.prepare(sql);
1390 		auto payload = RCPreparedPayload(prepared, conn);
1391 		return refCounted(move(payload));
1392 	}
1393 
1394 	@("rcPrepared")
1395 	unittest
1396 	{
1397 		import core.memory;
1398 		mixin(scopedCn);
1399 		
1400 		cn.exec("DROP TABLE IF EXISTS `rcPrepared`");
1401 		cn.exec("CREATE TABLE `rcPrepared` (
1402 			`val` INTEGER
1403 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1404 		cn.exec("INSERT INTO `rcPrepared` VALUES (1), (2), (3)");
1405 
1406 		// Define this in outer scope to guarantee data is left pending when
1407 		// RCPrepared's payload is collected. This will guarantee
1408 		// that Connection will need to queue the release.
1409 		ResultRange rows;
1410 
1411 		void bar()
1412 		{
1413 			class Foo { RCPrepared p; }
1414 			auto foo = new Foo();
1415 
1416 			auto rcStmt = cn.rcPrepare("SELECT * FROM `rcPrepared`");
1417 			foo.p = rcStmt;
1418 			rows = cn.query(rcStmt);
1419 
1420 			/+
1421 			At this point, there are two references to the prepared statement:
1422 			One in a `Foo` object (currently bound to `foo`), and one on the stack.
1423 
1424 			Returning from this function will destroy the one on the stack,
1425 			and deterministically reduce the refcount to 1.
1426 
1427 			So, right here we set `foo` to null to *keep* the Foo object's
1428 			reference to the prepared statement, but set adrift the Foo object
1429 			itself, ready to be destroyed (along with the only remaining
1430 			prepared statement reference it contains) by the next GC cycle.
1431 
1432 			Thus, `RCPreparedPayload.~this` and `Connection.release(Prepared)`
1433 			will be executed during a GC cycle...and had better not perform
1434 			any allocations, or else...boom!
1435 			+/
1436 			foo = null;
1437 		}
1438 
1439 		bar();
1440 		assert(cn.hasPending); // Ensure Connection is forced to queue the release.
1441 		GC.collect(); // `Connection.release(Prepared)` better not be allocating, or boom!
1442 	}
1443 }