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 }