1 /++
2 Connect to a MySQL/MariaDB database using a connection pool.
3 
4 This provides various benefits over creating a new connection manually,
5 such as automatically reusing old connections, and automatic cleanup (no need to close
6 the connection when done).
7 
8 Internally, this is based on vibe.d's
9 $(LINK2 http://vibed.org/api/vibe.core.connectionpool/ConnectionPool, ConnectionPool).
10 You have to include vibe.d in your project to be able to use this class.
11 If you don't want to, refer to `mysql.connection.Connection`.
12 
13 WARNING:
14 This module is used to consolidate the common implementation of the safe and
15 unafe API. DO NOT directly import this module, please import one of
16 `mysql.pool`, `mysql.safe.pool`, or `mysql.unsafe.pool`. This module will be
17 removed in a future version without deprecation.
18 
19 $(SAFE_MIGRATION)
20 +/
21 module mysql.impl.pool;
22 
23 import std.conv;
24 import std.typecons;
25 import mysql.impl.connection;
26 import mysql.impl.prepared;
27 import mysql.protocol.constants;
28 
29 version(Have_vibe_core)
30 {
31 	version = IncludeMySQLPool;
32 	static if(is(typeof(ConnectionPool!Connection.init.removeUnused((c){}))))
33 		version = HaveCleanupFunction;
34 }
35 version(MySQLDocs)
36 {
37 	version = IncludeMySQLPool;
38 	version = HaveCleanupFunction;
39 }
40 
41 version(IncludeMySQLPool)
42 {
43 	version(Have_vibe_core)
44 		import vibe.core.connectionpool;
45 	else version(MySQLDocs)
46 	{
47 		/++
48 		Vibe.d's
49 		$(LINK2 http://vibed.org/api/vibe.core.connectionpool/ConnectionPool, ConnectionPool)
50 		class.
51 
52 		Not actually included in module `mysql.pool`. Only listed here for
53 		documentation purposes. For ConnectionPool and it's documentation, see:
54 		$(LINK http://vibed.org/api/vibe.core.connectionpool/ConnectionPool)
55 		+/
56 		class ConnectionPool(T)
57 		{
58 			/// See: $(LINK http://vibed.org/api/vibe.core.connectionpool/ConnectionPool.this)
59 			this(Connection delegate() connection_factory, uint max_concurrent = (uint).max)
60 			{}
61 
62 			/// See: $(LINK http://vibed.org/api/vibe.core.connectionpool/ConnectionPool.lockConnection)
63 			LockedConnection!T lockConnection() { return LockedConnection!T(); }
64 
65 			/// See: $(LINK http://vibed.org/api/vibe.core.connectionpool/ConnectionPool.maxConcurrency)
66 			uint maxConcurrency;
67 
68 			/// See: $(LINK https://github.com/vibe-d/vibe-core/blob/24a83434e4c788ebb9859dfaecbe60ad0f6e9983/source/vibe/core/connectionpool.d#L113)
69 			void removeUnused(scope void delegate(Connection conn) @safe nothrow disconnect_callback)
70 			{}
71 		}
72 
73 		/++
74 		Vibe.d's
75 		$(LINK2 http://vibed.org/api/vibe.core.connectionpool/LockedConnection, LockedConnection)
76 		struct.
77 
78 		Not actually included in module `mysql.pool`. Only listed here for
79 		documentation purposes. For LockedConnection and it's documentation, see:
80 		$(LINK http://vibed.org/api/vibe.core.connectionpool/LockedConnection)
81 		+/
82 		struct LockedConnection(Connection) { Connection c; alias c this; }
83 	}
84 
85 	/++
86 	Connect to a MySQL/MariaDB database using a connection pool.
87 
88 	This provides various benefits over creating a new connection manually,
89 	such as automatically reusing old connections, and automatic cleanup (no need to close
90 	the connection when done).
91 
92 	Internally, this is based on vibe.d's
93 	$(LINK2 http://vibed.org/api/vibe.core.connectionpool/ConnectionPool, ConnectionPool).
94 	You have to include vibe.d in your project to be able to use this class.
95 	If you don't want to, refer to `mysql.connection.Connection`.
96 
97 	You should not use this template directly, but rather import
98 	`mysql.safe.pool` or `mysql.unsafe.pool` or `mysql.pool`, which will alias
99 	MySQLPool to the correct instantiation. The boolean parameter here
100 	specifies whether the pool is operating in safe mode or unsafe mode.
101 	+/
102 	class MySQLPoolImpl(bool isSafe)
103 	{
104 		private
105 		{
106 			string m_host;
107 			string m_user;
108 			string m_password;
109 			string m_database;
110 			ushort m_port;
111 			SvrCapFlags m_capFlags;
112 			static if(isSafe)
113 				alias NewConnectionDelegate = void delegate(Connection) @safe;
114 			else
115 				alias NewConnectionDelegate = void delegate(Connection) @system;
116 			NewConnectionDelegate m_onNewConnection;
117 			ConnectionPool!Connection m_pool;
118 			PreparedRegistrations!PreparedInfo preparedRegistrations;
119 
120 			struct PreparedInfo
121 			{
122 				bool queuedForRelease = false;
123 			}
124 
125 		}
126 
127 		/// Sets up a connection pool with the provided connection settings.
128 		///
129 		/// The optional `onNewConnection` param allows you to set a callback
130 		/// which will be run every time a new connection is created.
131 		this(string host, string user, string password, string database,
132 			ushort port = 3306, uint maxConcurrent = (uint).max,
133 			SvrCapFlags capFlags = defaultClientFlags,
134 			NewConnectionDelegate onNewConnection = null)
135 		{
136 			m_host = host;
137 			m_user = user;
138 			m_password = password;
139 			m_database = database;
140 			m_port = port;
141 			m_capFlags = capFlags;
142 			m_onNewConnection = onNewConnection;
143 			m_pool = new ConnectionPool!Connection(&createConnection);
144 		}
145 
146 		///ditto
147 		this(string host, string user, string password, string database,
148 			ushort port, SvrCapFlags capFlags, NewConnectionDelegate onNewConnection = null)
149 		{
150 			this(host, user, password, database, port, (uint).max, capFlags, onNewConnection);
151 		}
152 
153 		///ditto
154 		this(string host, string user, string password, string database,
155 			ushort port, NewConnectionDelegate onNewConnection)
156 		{
157 			this(host, user, password, database, port, (uint).max, defaultClientFlags, onNewConnection);
158 		}
159 
160 		///ditto
161 		this(string connStr, uint maxConcurrent = (uint).max, SvrCapFlags capFlags = defaultClientFlags,
162 			NewConnectionDelegate onNewConnection = null)
163 		{
164 			auto parts = Connection.parseConnectionString(connStr);
165 			this(parts[0], parts[1], parts[2], parts[3], to!ushort(parts[4]), capFlags, onNewConnection);
166 		}
167 
168 		///ditto
169 		this(string connStr, SvrCapFlags capFlags, NewConnectionDelegate onNewConnection = null)
170 		{
171 			this(connStr, (uint).max, capFlags, onNewConnection);
172 		}
173 
174 		///ditto
175 		this(string connStr, NewConnectionDelegate onNewConnection)
176 		{
177 			this(connStr, (uint).max, defaultClientFlags, onNewConnection);
178 		}
179 
180 		/++
181 		Obtain a connection. If one isn't available, a new one will be created.
182 
183 		The connection returned is actually a `LockedConnection!Connection`,
184 		but it uses `alias this`, and so can be used just like a Connection.
185 		(See vibe.d's
186 		$(LINK2 http://vibed.org/api/vibe.core.connectionpool/LockedConnection, LockedConnection documentation).)
187 
188 		No other fiber will be given this `mysql.connection.Connection` as long as your fiber still holds it.
189 
190 		There is no need to close, release or unlock this connection. It is
191 		reference-counted and will automatically be returned to the pool once
192 		your fiber is done with it.
193 
194 		If you have passed any prepared statements to  `autoRegister`
195 		or `autoRelease`, then those statements will automatically be
196 		registered/released on the connection. (Currently, this automatic
197 		register/release may actually occur upon the first command sent via
198 		the connection.)
199 		+/
200 		static if(isSafe)
201 			LockedConnection!Connection lockConnection() @safe
202 			{
203 				return lockConnectionImpl();
204 			}
205 		else
206 			LockedConnection!Connection lockConnection()
207 			{
208 				return lockConnectionImpl();
209 			}
210 
211 		// the implementation we want to infer attributes
212 		private final lockConnectionImpl()
213 		{
214 			auto conn = m_pool.lockConnection();
215 			if(conn.closed)
216 				conn.reconnect();
217 
218 			applyAuto(conn);
219 			return conn;
220 		}
221 
222 		/// Applies any `autoRegister`/`autoRelease` settings to a connection,
223 		/// if necessary.
224 		package(mysql) void applyAuto(T)(T conn)
225 		{
226 			foreach(sql, info; preparedRegistrations.directLookup)
227 			{
228 				auto registeredOnPool = !info.queuedForRelease;
229 				auto registeredOnConnection = conn.isRegistered(sql);
230 
231 				if(registeredOnPool && !registeredOnConnection) // Need to register?
232 					conn.register(sql);
233 				else if(!registeredOnPool && registeredOnConnection) // Need to release?
234 					conn.release(sql);
235 			}
236 		}
237 
238 		private Connection createConnection()
239 		{
240 			auto conn = new Connection(m_host, m_user, m_password, m_database, m_port, m_capFlags);
241 
242 			if(m_onNewConnection)
243 				m_onNewConnection(conn);
244 
245 			return conn;
246 		}
247 
248 		/// Get/set a callback delegate to be run every time a new connection
249 		/// is created.
250 		@property void onNewConnection(NewConnectionDelegate onNewConnection) @safe
251 		{
252 			m_onNewConnection = onNewConnection;
253 		}
254 
255 		///ditto
256 		@property NewConnectionDelegate onNewConnection() @safe
257 		{
258 			return m_onNewConnection;
259 		}
260 
261 		/++
262 		Forwards to vibe.d's
263 		$(LINK2 http://vibed.org/api/vibe.core.connectionpool/ConnectionPool.maxConcurrency, ConnectionPool.maxConcurrency)
264 		+/
265 		@property uint maxConcurrency() @safe
266 		{
267 			return m_pool.maxConcurrency;
268 		}
269 
270 		///ditto
271 		@property void maxConcurrency(uint maxConcurrent) @safe
272 		{
273 			m_pool.maxConcurrency = maxConcurrent;
274 		}
275 
276 		/++
277 		Set a prepared statement to be automatically registered on all
278 		connections received from this pool.
279 
280 		This also clears any `autoRelease` which may have been set for this statement.
281 
282 		Calling this is not strictly necessary, as a prepared statement will
283 		automatically be registered upon its first use on any `Connection`.
284 		This is provided for those who prefer eager registration over lazy
285 		for performance reasons.
286 
287 		Once this has been called, obtaining a connection via `lockConnection`
288 		will automatically register the prepared statement on the connection
289 		if it isn't already registered on the connection. This single
290 		registration safely persists after the connection is reclaimed by the
291 		pool and locked again by another Vibe.d task.
292 
293 		Note, due to the way Vibe.d works, it is not possible to eagerly
294 		register or release a statement on all connections already sitting
295 		in the pool. This can only be done when locking a connection.
296 
297 		You can stop the pool from continuing to auto-register the statement
298 		by calling either `autoRelease` or `clearAuto`.
299 		+/
300 		void autoRegister(SafePrepared prepared) @safe
301 		{
302 			autoRegister(prepared.sql);
303 		}
304 
305 		///ditto
306 		void autoRegister(UnsafePrepared prepared) @safe
307 		{
308 			autoRegister(prepared.sql);
309 		}
310 
311 		///ditto
312 		void autoRegister(const(char[]) sql) @safe
313 		{
314 			preparedRegistrations.registerIfNeeded(sql, (sql) => PreparedInfo());
315 		}
316 
317 		/++
318 		Set a prepared statement to be automatically released from all
319 		connections received from this pool.
320 
321 		This also clears any `autoRegister` which may have been set for this statement.
322 
323 		Calling this is not strictly necessary. The server considers prepared
324 		statements to be per-connection, so they'll go away when the connection
325 		closes anyway. This is provided in case direct control is actually needed.
326 
327 		Once this has been called, obtaining a connection via `lockConnection`
328 		will automatically release the prepared statement from the connection
329 		if it isn't already releases from the connection.
330 
331 		Note, due to the way Vibe.d works, it is not possible to eagerly
332 		register or release a statement on all connections already sitting
333 		in the pool. This can only be done when locking a connection.
334 
335 		You can stop the pool from continuing to auto-release the statement
336 		by calling either `autoRegister` or `clearAuto`.
337 		+/
338 		void autoRelease(SafePrepared prepared) @safe
339 		{
340 			autoRelease(prepared.sql);
341 		}
342 
343 		///ditto
344 		void autoRelease(UnsafePrepared prepared) @safe
345 		{
346 			autoRelease(prepared.sql);
347 		}
348 
349 		///ditto
350 		void autoRelease(const(char[]) sql) @safe
351 		{
352 			preparedRegistrations.queueForRelease(sql);
353 		}
354 
355 		/// Is the given statement set to be automatically registered on all
356 		/// connections obtained from this connection pool?
357 		bool isAutoRegistered(SafePrepared prepared) @safe
358 		{
359 			return isAutoRegistered(prepared.sql);
360 		}
361 		///ditto
362 		bool isAutoRegistered(UnsafePrepared prepared) @safe
363 		{
364 			return isAutoRegistered(prepared.sql);
365 		}
366 		///ditto
367 		bool isAutoRegistered(const(char[]) sql) @safe
368 		{
369 			return isAutoRegistered(preparedRegistrations[sql]);
370 		}
371 		///ditto
372 		package bool isAutoRegistered(Nullable!PreparedInfo info) @safe
373 		{
374 			return info.isNull || !info.get.queuedForRelease;
375 		}
376 
377 		/// Is the given statement set to be automatically released on all
378 		/// connections obtained from this connection pool?
379 		bool isAutoReleased(SafePrepared prepared) @safe
380 		{
381 			return isAutoReleased(prepared.sql);
382 		}
383 		///ditto
384 		bool isAutoReleased(UnsafePrepared prepared) @safe
385 		{
386 			return isAutoReleased(prepared.sql);
387 		}
388 		///ditto
389 		bool isAutoReleased(const(char[]) sql) @safe
390 		{
391 			return isAutoReleased(preparedRegistrations[sql]);
392 		}
393 		///ditto
394 		package bool isAutoReleased(Nullable!PreparedInfo info) @safe
395 		{
396 			return info.isNull || info.get.queuedForRelease;
397 		}
398 
399 		/++
400 		Is the given statement set for NEITHER auto-register
401 		NOR auto-release on connections obtained from
402 		this connection pool?
403 
404 		Equivalent to `!isAutoRegistered && !isAutoReleased`.
405 		+/
406 		bool isAutoCleared(SafePrepared prepared) @safe
407 		{
408 			return isAutoCleared(prepared.sql);
409 		}
410 		///ditto
411 		bool isAutoCleared(const(char[]) sql) @safe
412 		{
413 			return isAutoCleared(preparedRegistrations[sql]);
414 		}
415 		///ditto
416 		package bool isAutoCleared(Nullable!PreparedInfo info) @safe
417 		{
418 			return info.isNull;
419 		}
420 
421 		/++
422 		Removes any `autoRegister` or `autoRelease` which may have been set
423 		for this prepared statement.
424 
425 		Does nothing if the statement has not been set for auto-register or auto-release.
426 
427 		This releases any relevent memory for potential garbage collection.
428 		+/
429 		void clearAuto(SafePrepared prepared) @safe
430 		{
431 			return clearAuto(prepared.sql);
432 		}
433 		///ditto
434 		void clearAuto(UnsafePrepared prepared) @safe
435 		{
436 			return clearAuto(prepared.sql);
437 		}
438 		///ditto
439 		void clearAuto(const(char[]) sql) @safe
440 		{
441 			preparedRegistrations.directLookup.remove(sql);
442 		}
443 
444 		/++
445 		Removes ALL prepared statement `autoRegister` and `autoRelease` which have been set.
446 
447 		This releases all relevent memory for potential garbage collection.
448 		+/
449 		void clearAllRegistrations() @safe
450 		{
451 			preparedRegistrations.clear();
452 		}
453 
454 		version(MySQLDocs)
455 		{
456 			/++
457 			Removes all unused connections from the pool. This can
458 			be used to clean up before exiting the program to
459 			ensure the event core driver can be properly shut down.
460 
461 			Note: this is only available if vibe-core 1.7.0 or later is being
462 			used.
463 			+/
464 			void removeUnusedConnections() @safe {}
465 		}
466 		else version(HaveCleanupFunction)
467 		{
468 			void removeUnusedConnections() @safe
469 			{
470 				// Note: we squelch all exceptions here, because vibe-core
471 				// requires the function be nothrow, and because an exception
472 				// thrown while closing is probably not important enough to
473 				// interrupt cleanup.
474 				m_pool.removeUnused((conn) @trusted nothrow {
475 					try {
476 						conn.close();
477 					} catch(Exception) {}
478 				});
479 			}
480 		}
481 	}
482 }