1 /++
2 Implementation - Connection class.
3 
4 WARNING:
5 This module is used to consolidate the common implementation of the safe and
6 unafe API. DO NOT directly import this module, please import one of
7 `mysql.connection`, `mysql.safe.connection`, or `mysql.unsafe.connection`. This
8 module will be removed in a future version without deprecation.
9 
10 $(SAFE_MIGRATION)
11 +/
12 module mysql.impl.connection;
13 
14 import std.algorithm;
15 import std.conv;
16 import std.exception;
17 import std.range;
18 import std.socket;
19 import std.string;
20 import std.typecons;
21 
22 import mysql.exceptions;
23 import mysql.logger;
24 import mysql.protocol.comms;
25 import mysql.protocol.constants;
26 import mysql.protocol.packets;
27 import mysql.protocol.sockets;
28 import mysql.impl.result;
29 import mysql.impl.prepared;
30 import mysql.types;
31 
32 @safe:
33 
34 version(Have_vibe_core)
35 {
36 	static if(__traits(compiles, (){ import vibe.core.net; } ))
37 		import vibe.core.net;
38 	else
39 		static assert(false, "mysql-native can't find Vibe.d's 'vibe.core.net'.");
40 }
41 
42 /// The default `mysql.protocol.constants.SvrCapFlags` used when creating a connection.
43 immutable SvrCapFlags defaultClientFlags =
44 		SvrCapFlags.OLD_LONG_PASSWORD | SvrCapFlags.ALL_COLUMN_FLAGS |
45 		SvrCapFlags.WITH_DB | SvrCapFlags.PROTOCOL41 |
46 		SvrCapFlags.SECURE_CONNECTION;// | SvrCapFlags.MULTI_STATEMENTS |
47 		//SvrCapFlags.MULTI_RESULTS;
48 
49 package(mysql) string preparedPlaceholderArgs(int numArgs)
50 {
51 	auto sql = "(";
52 	bool comma = false;
53 	foreach(i; 0..numArgs)
54 	{
55 		if (comma)
56 			sql ~= ",?";
57 		else
58 		{
59 			sql ~= "?";
60 			comma = true;
61 		}
62 	}
63 	sql ~= ")";
64 
65 	return sql;
66 }
67 
68 @("preparedPlaceholderArgs")
69 debug(MYSQLN_TESTS)
70 unittest
71 {
72 	assert(preparedPlaceholderArgs(3) == "(?,?,?)");
73 	assert(preparedPlaceholderArgs(2) == "(?,?)");
74 	assert(preparedPlaceholderArgs(1) == "(?)");
75 	assert(preparedPlaceholderArgs(0) == "()");
76 }
77 
78 /// Per-connection info from the server about a registered prepared statement.
79 package(mysql) struct PreparedServerInfo
80 {
81 	/// Server's identifier for this prepared statement.
82 	/// Apperently, this is never 0 if it's been registered,
83 	/// although mysql-native no longer relies on that.
84 	uint statementId;
85 
86 	ushort psWarnings;
87 
88 	/// Number of parameters this statement takes.
89 	///
90 	/// This will be the same on all connections, but it's returned
91 	/// by the server upon registration, so it's stored here.
92 	ushort numParams;
93 
94 	/// Prepared statement headers
95 	///
96 	/// This will be the same on all connections, but it's returned
97 	/// by the server upon registration, so it's stored here.
98 	PreparedStmtHeaders headers;
99 
100 	/// Not actually from the server. Connection uses this to keep track
101 	/// of statements that should be treated as having been released.
102 	bool queuedForRelease = false;
103 }
104 
105 /++
106 A class representing a database connection.
107 
108 If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of
109 creating a new Connection directly. That will provide certain benefits,
110 such as reusing old connections and automatic cleanup (no need to close
111 the connection when done).
112 
113 ------------------
114 // Suggested usage:
115 
116 {
117 	auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb");
118 	scope(exit) con.close();
119 
120 	// Use the connection
121 	...
122 }
123 ------------------
124 +/
125 //TODO: All low-level commms should be moved into the mysql.protocol package.
126 class Connection
127 {
128 	@safe:
129 /+
130 The Connection is responsible for handshaking with the server to establish
131 authentication. It then passes client preferences to the server, and
132 subsequently is the channel for all command packets that are sent, and all
133 response packets received.
134 
135 Uncompressed packets consist of a 4 byte header - 3 bytes of length, and one
136 byte as a packet number. Connection deals with the headers and ensures that
137 packet numbers are sequential.
138 
139 The initial packet is sent by the server - essentially a 'hello' packet
140 inviting login. That packet has a sequence number of zero. That sequence
141 number is the incremented by client and server packets through the handshake
142 sequence.
143 
144 After login all further sequences are initialized by the client sending a
145 command packet with a zero sequence number, to which the server replies with
146 zero or more packets with sequential sequence numbers.
147 +/
148 package(mysql):
149 	enum OpenState
150 	{
151 		/// We have not yet connected to the server, or have sent QUIT to the
152 		/// server and closed the connection
153 		notConnected,
154 		/// We have connected to the server and parsed the greeting, but not
155 		/// yet authenticated
156 		connected,
157 		/// We have successfully authenticated against the server, and need to
158 		/// send QUIT to the server when closing the connection
159 		authenticated
160 	}
161 	OpenState   _open;
162 	MySQLSocket _socket;
163 
164 	SvrCapFlags _sCaps, _cCaps;
165 	uint    _sThread;
166 	ushort  _serverStatus;
167 	ubyte   _sCharSet, _protocol;
168 	string  _serverVersion;
169 
170 	string _host, _user, _pwd, _db;
171 	ushort _port;
172 
173 	MySQLSocketType _socketType;
174 
175 	OpenSocketCallbackPhobos _openSocketPhobos;
176 	OpenSocketCallbackVibeD  _openSocketVibeD;
177 
178 	ulong _insertID;
179 
180 	// This gets incremented every time a command is issued or results are purged,
181 	// so a ResultRange can tell whether it's been invalidated.
182 	ulong _lastCommandID;
183 
184 	// Whether there are rows, headers or bimary data waiting to be retreived.
185 	// MySQL protocol doesn't permit performing any other action until all
186 	// such data is read.
187 	bool _rowsPending, _headersPending, _binaryPending;
188 
189 	// Field count of last performed command.
190 	//TODO: Does Connection need to store this?
191 	ushort _fieldCount;
192 
193 	// ResultSetHeaders of last performed command.
194 	//TODO: Does Connection need to store this? Is this even used?
195 	ResultSetHeaders _rsh;
196 
197 	// This tiny thing here is pretty critical. Pay great attention to it's maintenance, otherwise
198 	// you'll get the dreaded "packet out of order" message. It, and the socket connection are
199 	// the reason why most other objects require a connection object for their construction.
200 	ubyte _cpn; /// Packet Number in packet header. Serial number to ensure correct
201 				/// ordering. First packet should have 0
202 	@property ubyte pktNumber()   { return _cpn; }
203 	void bumpPacket()       { _cpn++; }
204 	void resetPacket()      { _cpn = 0; }
205 
206 	version(Have_vibe_core) {} else
207 	pure const nothrow invariant()
208 	{
209 		assert(_socketType != MySQLSocketType.vibed);
210 	}
211 
212 	static PlainPhobosSocket defaultOpenSocketPhobos(string host, ushort port)
213 	{
214 		logDebug("opening phobos socket %s:%d", host, port);
215 		auto s = new PlainPhobosSocket();
216 		s.connect(new InternetAddress(host, port));
217 		s.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, true);
218 		s.setOption(SocketOptionLevel.SOCKET, SocketOption.KEEPALIVE, true);
219 		return s;
220 	}
221 
222 	static PlainVibeDSocket defaultOpenSocketVibeD(string host, ushort port)
223 	{
224 		version(Have_vibe_core)
225 		{
226 			logDebug("opening vibe-d socket %s:%d", host, port);
227 			auto s = vibe.core.net.connectTCP(host, port);
228 			s.tcpNoDelay = true;
229 			s.keepAlive = true;
230 			return s;
231 		}
232 		else
233 			assert(0);
234 	}
235 
236 	void initConnection()
237 	{
238 		kill(); // Ensure internal state gets reset
239 
240 		resetPacket();
241 		final switch(_socketType)
242 		{
243 			case MySQLSocketType.phobos:
244 				_socket = new MySQLSocketPhobos(_openSocketPhobos(_host, _port));
245 				break;
246 
247 			case MySQLSocketType.vibed:
248 				version(Have_vibe_core) {
249 					_socket = new MySQLSocketVibeD(_openSocketVibeD(_host, _port));
250 					break;
251 				} else assert(0, "Unsupported socket type. Need version Have_vibe_core.");
252 		}
253 	}
254 
255 	SvrCapFlags _clientCapabilities;
256 
257 	void connect(SvrCapFlags clientCapabilities)
258 	out
259 	{
260 		assert(_open == OpenState.authenticated);
261 	}
262 	do
263 	{
264 		initConnection();
265 		auto greeting = this.parseGreeting();
266 		_open = OpenState.connected;
267 
268 		_clientCapabilities = clientCapabilities;
269 		_cCaps = setClientFlags(_sCaps, clientCapabilities);
270 		this.authenticate(greeting);
271 	}
272 
273 	/++
274 	Forcefully close the socket without sending the quit command.
275 
276 	Also resets internal state regardless of whether the connection is open or not.
277 
278 	Needed in case an error leaves communatations in an undefined or non-recoverable state.
279 	+/
280 	void kill()
281 	{
282 		if(_socket && _socket.connected)
283 			_socket.close();
284 		_open = OpenState.notConnected;
285 		// any pending data is gone. Any statements to release will be released
286 		// on the server automatically.
287 		_headersPending = _rowsPending = _binaryPending = false;
288 
289 		preparedRegistrations.clear();
290 
291 		_lastCommandID++; // Invalidate result sets
292 	}
293 
294 	// autoPurge is called every time a command is sent,
295 	// so detect & prevent infinite recursion.
296 	private bool isAutoPurging = false;
297 
298 
299 	/// Called whenever mysql-native needs to send a command to the server
300 	/// and be sure there aren't any pending results (which would prevent
301 	/// a new command from being sent).
302 	void autoPurge()
303 	{
304 		if(isAutoPurging)
305 			return;
306 
307 		isAutoPurging = true;
308 		scope(exit) isAutoPurging = false;
309 
310 		try
311 		{
312 			purgeResult();
313 			releaseQueued();
314 		}
315 		catch(Exception e)
316 		{
317 			// Likely the connection was closed, so reset any state (and force-close if needed).
318 			// Don't treat this as a real error, because everything will be reset when we
319 			// reconnect.
320 			kill();
321 		}
322 	}
323 
324 	/// Lookup per-connection prepared statement info by SQL
325 	private PreparedRegistrations!PreparedServerInfo preparedRegistrations;
326 
327 	/// Releases all prepared statements that are queued for release.
328 	void releaseQueued()
329 	{
330 		foreach(sql, info; preparedRegistrations.directLookup)
331 		if(info.queuedForRelease)
332 		{
333 			immediateReleasePrepared(this, info.statementId);
334 			preparedRegistrations.directLookup.remove(sql);
335 		}
336 	}
337 
338 	/// Returns null if not found
339 	Nullable!PreparedServerInfo getPreparedServerInfo(const(char[]) sql) pure nothrow
340 	{
341 		return preparedRegistrations[sql];
342 	}
343 
344 	/// If already registered, simply returns the cached `PreparedServerInfo`.
345 	PreparedServerInfo registerIfNeeded(const(char[]) sql)
346 	{
347 		return preparedRegistrations.registerIfNeeded(sql, sql => performRegister(this, sql));
348 	}
349 
350 public:
351 
352 	/++
353 	Construct opened connection.
354 
355 	Throws `mysql.exceptions.MYX` upon failure to connect.
356 
357 	If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of
358 	creating a new Connection directly. That will provide certain benefits,
359 	such as reusing old connections and automatic cleanup (no need to close
360 	the connection when done).
361 
362 	------------------
363 	// Suggested usage:
364 
365 	{
366 	    auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb");
367 	    scope(exit) con.close();
368 
369 	    // Use the connection
370 	    ...
371 	}
372 	------------------
373 
374 	Params:
375 		cs = A connection string of the form "host=localhost;user=user;pwd=password;db=mysqld"
376 			(TODO: The connection string needs work to allow for semicolons in its parts!)
377 		socketType = Whether to use a Phobos or Vibe.d socket. Default is Phobos,
378 			unless compiled with `-version=Have_vibe_core` (set automatically
379 			if using $(LINK2 http://code.dlang.org/getting_started, DUB)).
380 		openSocket = Optional callback which should return a newly-opened Phobos
381 			or Vibe.d TCP socket. This allows custom sockets to be used,
382 			subclassed from Phobos's or Vibe.d's sockets.
383 		host = An IP address in numeric dotted form, or as a host  name.
384 		user = The user name to authenticate.
385 		pwd = User's password.
386 		db = Desired initial database.
387 		capFlags = The set of flag bits from the server's capabilities that the client requires
388 	+/
389 	//After the connection is created, and the initial invitation is received from the server
390 	//client preferences can be set, and authentication can then be attempted.
391 	this(string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
392 	{
393 		version(Have_vibe_core)
394 			enum defaultSocketType = MySQLSocketType.vibed;
395 		else
396 			enum defaultSocketType = MySQLSocketType.phobos;
397 
398 		this(defaultSocketType, host, user, pwd, db, port, capFlags);
399 	}
400 
401 	///ditto
402 	this(MySQLSocketType socketType, string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
403 	{
404 		version(Have_vibe_core) {} else
405 			enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_core");
406 
407 		this(socketType, &defaultOpenSocketPhobos, &defaultOpenSocketVibeD,
408 			host, user, pwd, db, port, capFlags);
409 	}
410 
411 	///ditto
412 	this(OpenSocketCallbackPhobos openSocket,
413 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
414 	{
415 		this(MySQLSocketType.phobos, openSocket, null, host, user, pwd, db, port, capFlags);
416 	}
417 
418 	version(Have_vibe_core)
419 	///ditto
420 	this(OpenSocketCallbackVibeD openSocket,
421 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
422 	{
423 		this(MySQLSocketType.vibed, null, openSocket, host, user, pwd, db, port, capFlags);
424 	}
425 
426 	///ditto
427 	private this(MySQLSocketType socketType,
428 		OpenSocketCallbackPhobos openSocketPhobos, OpenSocketCallbackVibeD openSocketVibeD,
429 		string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags)
430 	in
431 	{
432 		final switch(socketType)
433 		{
434 			case MySQLSocketType.phobos: assert(openSocketPhobos !is null); break;
435 			case MySQLSocketType.vibed:  assert(openSocketVibeD  !is null); break;
436 		}
437 	}
438 	do
439 	{
440 		enforce!MYX(capFlags & SvrCapFlags.PROTOCOL41, "This client only supports protocol v4.1");
441 		enforce!MYX(capFlags & SvrCapFlags.SECURE_CONNECTION, "This client only supports protocol v4.1 connection");
442 		version(Have_vibe_core) {} else
443 			enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_core");
444 
445 		_socketType = socketType;
446 		_host = host;
447 		_user = user;
448 		_pwd = pwd;
449 		_db = db;
450 		_port = port;
451 
452 		_openSocketPhobos = openSocketPhobos;
453 		_openSocketVibeD  = openSocketVibeD;
454 
455 		connect(capFlags);
456 	}
457 
458 	///ditto
459 	//After the connection is created, and the initial invitation is received from the server
460 	//client preferences can be set, and authentication can then be attempted.
461 	this(string cs, SvrCapFlags capFlags = defaultClientFlags)
462 	{
463 		string[] a = parseConnectionString(cs);
464 		this(a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
465 	}
466 
467 	///ditto
468 	this(MySQLSocketType socketType, string cs, SvrCapFlags capFlags = defaultClientFlags)
469 	{
470 		string[] a = parseConnectionString(cs);
471 		this(socketType, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
472 	}
473 
474 	///ditto
475 	this(OpenSocketCallbackPhobos openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags)
476 	{
477 		string[] a = parseConnectionString(cs);
478 		this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
479 	}
480 
481 	version(Have_vibe_core)
482 	///ditto
483 	this(OpenSocketCallbackVibeD openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags)
484 	{
485 		string[] a = parseConnectionString(cs);
486 		this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags);
487 	}
488 
489 	/++
490 	Check whether this `Connection` is still connected to the server, or if
491 	the connection has been closed.
492 	+/
493 	@property bool closed()
494 	{
495 		return _open == OpenState.notConnected || !_socket.connected;
496 	}
497 
498 	/++
499 	Explicitly close the connection.
500 
501 	Idiomatic use as follows is suggested:
502 	------------------
503 	{
504 	    auto con = new Connection("localhost:user:password:mysqld");
505 	    scope(exit) con.close();
506 	    // Use the connection
507 	    ...
508 	}
509 	------------------
510 	+/
511 	void close()
512 	{
513 		// This is a two-stage process. First tell the server we are quitting this
514 		// connection, and then close the socket.
515 
516 		if (_open == OpenState.authenticated && _socket.connected)
517 			quit();
518 
519 		if (_open == OpenState.connected)
520 			kill();
521 		resetPacket();
522 	}
523 
524 	/++
525 	Reconnects to the server using the same connection settings originally
526 	used to create the `Connection`.
527 
528 	Optionally takes a `mysql.protocol.constants.SvrCapFlags`, allowing you to
529 	reconnect using a different set of server capability flags.
530 
531 	Normally, if the connection is already open, this will do nothing. However,
532 	if you request a different set of `mysql.protocol.constants.SvrCapFlags`
533 	then was originally used to create the `Connection`, the connection will
534 	be closed and then reconnected using the new `mysql.protocol.constants.SvrCapFlags`.
535 	+/
536 	void reconnect()
537 	{
538 		reconnect(_clientCapabilities);
539 	}
540 
541 	///ditto
542 	void reconnect(SvrCapFlags clientCapabilities)
543 	{
544 		bool sameCaps = clientCapabilities == _clientCapabilities;
545 		if(!closed)
546 		{
547 			// Same caps as before?
548 			if(clientCapabilities == _clientCapabilities)
549 				return; // Nothing to do, just keep current connection
550 
551 			close();
552 		}
553 
554 		connect(clientCapabilities);
555 	}
556 
557 	private void quit()
558 	in
559 	{
560 		assert(_open == OpenState.authenticated);
561 	}
562 	do
563 	{
564 		this.sendCmd(CommandType.QUIT, []);
565 		// No response is sent for a quit packet
566 		_open = OpenState.connected;
567 	}
568 
569 	/++
570 	Parses a connection string of the form
571 	`"host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb"`
572 
573 	Port is optional and defaults to 3306.
574 
575 	Whitespace surrounding any name or value is automatically stripped.
576 
577 	Returns a five-element array of strings in this order:
578 	$(UL
579 	$(LI [0]: host)
580 	$(LI [1]: user)
581 	$(LI [2]: pwd)
582 	$(LI [3]: db)
583 	$(LI [4]: port)
584 	)
585 
586 	(TODO: The connection string needs work to allow for semicolons in its parts!)
587 	+/
588 	//TODO: Replace the return value with a proper struct.
589 	static string[] parseConnectionString(string cs)
590 	{
591 		string[] rv;
592 		rv.length = 5;
593 		rv[4] = "3306"; // Default port
594 		string[] a = split(cs, ";");
595 		foreach (s; a)
596 		{
597 			string[] a2 = split(s, "=");
598 			enforce!MYX(a2.length == 2, "Bad connection string: " ~ cs);
599 			string name = strip(a2[0]);
600 			string val = strip(a2[1]);
601 			switch (name)
602 			{
603 				case "host":
604 					rv[0] = val;
605 					break;
606 				case "user":
607 					rv[1] = val;
608 					break;
609 				case "pwd":
610 					rv[2] = val;
611 					break;
612 				case "db":
613 					rv[3] = val;
614 					break;
615 				case "port":
616 					rv[4] = val;
617 					break;
618 				default:
619 					throw new MYX("Bad connection string: " ~ cs, __FILE__, __LINE__);
620 			}
621 		}
622 		return rv;
623 	}
624 
625 	/++
626 	Select a current database.
627 
628 	Throws `mysql.exceptions.MYX` upon failure.
629 
630 	Params: dbName = Name of the requested database
631 	+/
632 	void selectDB(string dbName)
633 	{
634 		this.sendCmd(CommandType.INIT_DB, dbName);
635 		this.getCmdResponse();
636 		_db = dbName;
637 	}
638 
639 	/++
640 	Check the server status.
641 
642 	Throws `mysql.exceptions.MYX` upon failure.
643 
644 	Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined
645 	+/
646 	OKErrorPacket pingServer()
647 	{
648 		this.sendCmd(CommandType.PING, []);
649 		return this.getCmdResponse();
650 	}
651 
652 	/++
653 	Refresh some feature(s) of the server.
654 
655 	Throws `mysql.exceptions.MYX` upon failure.
656 
657 	Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined
658 	+/
659 	OKErrorPacket refreshServer(RefreshFlags flags)
660 	{
661 		this.sendCmd(CommandType.REFRESH, [flags]);
662 		return this.getCmdResponse();
663 	}
664 
665 	/++
666 	Flush any outstanding result set elements.
667 
668 	When the server responds to a command that produces a result set, it
669 	queues the whole set of corresponding packets over the current connection.
670 	Before that `Connection` can embark on any new command, it must receive
671 	all of those packets and junk them.
672 
673 	As of v1.1.4, this is done automatically as needed. But you can still
674 	call this manually to force a purge to occur when you want.
675 
676 	See_Also: $(LINK http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/)
677 	+/
678 	ulong purgeResult()
679 	{
680 		return mysql.protocol.comms.purgeResult(this);
681 	}
682 
683 	/++
684 	Get a textual report on the server status.
685 
686 	(COM_STATISTICS)
687 	+/
688 	string serverStats()
689 	{
690 		return mysql.protocol.comms.serverStats(this);
691 	}
692 
693 	/++
694 	Enable multiple statement commands.
695 
696 	This can be used later if this feature was not requested in the client capability flags.
697 
698 	Warning: This functionality is currently untested.
699 
700 	Params: on = Boolean value to turn the capability on or off.
701 	+/
702 	//TODO: Need to test this
703 	void enableMultiStatements(bool on)
704 	{
705 		mysql.protocol.comms.enableMultiStatements(this, on);
706 	}
707 
708 	/// Return the in-force protocol number.
709 	@property ubyte protocol() pure const nothrow { return _protocol; }
710 	/// Server version
711 	@property string serverVersion() pure const nothrow { return _serverVersion; }
712 	/// Server capability flags
713 	@property uint serverCapabilities() pure const nothrow { return _sCaps; }
714 	/// Server status
715 	@property ushort serverStatus() pure const nothrow { return _serverStatus; }
716 	/// Current character set
717 	@property ubyte charSet() pure const nothrow { return _sCharSet; }
718 	/// Current database
719 	@property string currentDB() pure const nothrow { return _db; }
720 	/// Socket type being used, Phobos or Vibe.d
721 	@property MySQLSocketType socketType() pure const nothrow { return _socketType; }
722 
723 	/// After a command that inserted a row into a table with an auto-increment
724 	/// ID column, this method allows you to retrieve the last insert ID.
725 	@property ulong lastInsertID() pure const nothrow { return _insertID; }
726 
727 	/// This gets incremented every time a command is issued or results are purged,
728 	/// so a `mysql.result.ResultRange` can tell whether it's been invalidated.
729 	@property ulong lastCommandID() pure const nothrow { return _lastCommandID; }
730 
731 	/// Gets whether rows are pending.
732 	///
733 	/// Note, you may want `hasPending` instead.
734 	@property bool rowsPending() pure const nothrow { return _rowsPending; }
735 
736 	/// Gets whether anything (rows, headers or binary) is pending.
737 	/// New commands cannot be sent on a connection while anything is pending
738 	/// (the pending data will automatically be purged.)
739 	@property bool hasPending() pure const nothrow
740 	{
741 		return _rowsPending || _headersPending || _binaryPending;
742 	}
743 
744 	/// Gets the result header's field descriptions.
745 	@property FieldDescription[] resultFieldDescriptions() pure { return _rsh.fieldDescriptions; }
746 
747 	/++
748 	Manually register a prepared statement on this connection.
749 
750 	Does nothing if statement is already registered on this connection.
751 
752 	Calling this is not strictly necessary, as the prepared statement will
753 	automatically be registered upon its first use on any `Connection`.
754 	This is provided for those who prefer eager registration over lazy
755 	for performance reasons.
756 	+/
757 	void register(SafePrepared prepared)
758 	{
759 		register(prepared.sql);
760 	}
761 
762 	///ditto
763 	void register(UnsafePrepared prepared)
764 	{
765 		register(prepared.sql);
766 	}
767 
768 	///ditto
769 	void register(const(char[]) sql)
770 	{
771 		registerIfNeeded(sql);
772 	}
773 
774 	/++
775 	Manually release a prepared statement on this connection.
776 
777 	This method tells the server that it can dispose of the information it
778 	holds about the current prepared statement.
779 
780 	Calling this is not strictly necessary. The server considers prepared
781 	statements to be per-connection, so they'll go away when the connection
782 	closes anyway. This is provided in case direct control is actually needed.
783 
784 	If you choose to use a reference counted struct to call this automatically,
785 	be aware that embedding reference counted structs inside garbage collectible
786 	heap objects is dangerous and should be avoided, as it can lead to various
787 	hidden problems, from crashes to race conditions. (See the discussion at issue
788 	$(LINK2 https://github.com/mysql-d/mysql-native/issues/159, #159)
789 	for details.) Instead, it may be better to simply avoid trying to manage
790 	their release at all, as it's not usually necessary. Or to periodically
791 	release all prepared statements, and simply allow mysql-native to
792 	automatically re-register them upon their next use.
793 
794 	Notes:
795 
796 	In actuality, the server might not immediately be told to release the
797 	statement (although `isRegistered` will still report `false`).
798 
799 	This is because there could be a `mysql.result.ResultRange` with results
800 	still pending for retrieval, and the protocol doesn't allow sending commands
801 	(such as "release a prepared statement") to the server while data is pending.
802 	Therefore, this function may instead queue the statement to be released
803 	when it is safe to do so: Either the next time a result set is purged or
804 	the next time a command (such as `mysql.commands.query` or
805 	`mysql.commands.exec`) is performed (because such commands automatically
806 	purge any pending results).
807 
808 	This function does NOT auto-purge because, if this is ever called from
809 	automatic resource management cleanup (refcounting, RAII, etc), that
810 	would create ugly situations where hidden, implicit behavior triggers
811 	an unexpected auto-purge.
812 	+/
813 	void release(SafePrepared prepared)
814 	{
815 		release(prepared.sql);
816 	}
817 
818 	///ditto
819 	void release(UnsafePrepared prepared)
820 	{
821 		release(prepared.sql);
822 	}
823 
824 	///ditto
825 	void release(const(char[]) sql)
826 	{
827 		//TODO: Don't queue it if nothing is pending. Just do it immediately.
828 		//      But need to be certain both situations are unittested.
829 		preparedRegistrations.queueForRelease(sql);
830 	}
831 
832 	/++
833 	Manually release all prepared statements on this connection.
834 
835 	While minimal, every prepared statement registered on a connection does
836 	use up a small amount of resources in both mysql-native and on the server.
837 	Additionally, servers can be configured
838 	$(LINK2 https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_prepared_stmt_count,
839 	to limit the number of prepared statements)
840 	allowed on a connection at one time (the default, however
841 	is quite high). Note also, that certain overloads of `mysql.commands.exec`,
842 	`mysql.commands.query`, etc. register prepared statements behind-the-scenes
843 	which are cached for quick re-use later.
844 
845 	Therefore, it may occasionally be useful to clear out all prepared
846 	statements on a connection, together with all resources used by them (or
847 	at least leave the resources ready for garbage-collection). This function
848 	does just that.
849 
850 	Note that this is ALWAYS COMPLETELY SAFE to call, even if you still have
851 	live prepared statements you intend to use again. This is safe because
852 	mysql-native will automatically register or re-register prepared statements
853 	as-needed.
854 
855 	Notes:
856 
857 	In actuality, the prepared statements might not be immediately released
858 	(although `isRegistered` will still report `false` for them).
859 
860 	This is because there could be a `mysql.result.ResultRange` with results
861 	still pending for retrieval, and the protocol doesn't allow sending commands
862 	(such as "release a prepared statement") to the server while data is pending.
863 	Therefore, this function may instead queue the statement to be released
864 	when it is safe to do so: Either the next time a result set is purged or
865 	the next time a command (such as `mysql.commands.query` or
866 	`mysql.commands.exec`) is performed (because such commands automatically
867 	purge any pending results).
868 
869 	This function does NOT auto-purge because, if this is ever called from
870 	automatic resource management cleanup (refcounting, RAII, etc), that
871 	would create ugly situations where hidden, implicit behavior triggers
872 	an unexpected auto-purge.
873 	+/
874 	void releaseAll()
875 	{
876 		preparedRegistrations.queueAllForRelease();
877 	}
878 
879 	/// Is the given statement registered on this connection as a prepared statement?
880 	bool isRegistered(SafePrepared prepared)
881 	{
882 		return isRegistered( prepared.sql );
883 	}
884 
885 	///ditto
886 	bool isRegistered(UnsafePrepared prepared)
887 	{
888 		return isRegistered( prepared.sql );
889 	}
890 
891 	///ditto
892 	bool isRegistered(const(char[]) sql)
893 	{
894 		return isRegistered( preparedRegistrations[sql] );
895 	}
896 
897 	///ditto
898 	package bool isRegistered(Nullable!PreparedServerInfo info)
899 	{
900 		return !info.isNull && !info.get.queuedForRelease;
901 	}
902 }