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