diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 7d36e79..dd4c0a2 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -135,6 +135,8 @@ */ protected $prefixReplace = array(); + protected $classReferences = array(); + function __construct($dsn, $username, $password, $driver_options = array()) { // Initialize and prepare the connection prefix. $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); @@ -147,7 +149,24 @@ function __construct($dsn, $username, $password, $driver_options = array()) { // Set a Statement class, unless the driver opted out. if (!empty($this->statementClass)) { - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this->connectionOptions['key'], $this->connectionOptions['target']))); + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); + } + } + + public function addClassReference($object) { + $this->classReferences[] = $object; + } + + public function destroy() { + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + + // Destroy all references to this connection in other classes by setting + // them to NULL. Prevent introducing new references in the local scope by + // iterating over array_keys(). +// debug(count($this->classReferences), '$this->classReferences'); + foreach (array_keys($this->classReferences) as $i) { +// debug(get_class($this->classReferences[$i])); + $this->classReferences[$i] = NULL; } } diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php index 9fb5cc5..35f0df5 100644 --- a/core/lib/Drupal/Core/Database/Database.php +++ b/core/lib/Drupal/Core/Database/Database.php @@ -374,14 +374,8 @@ public static function addConnectionInfo($key, $target, $info) { throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); } - // Allow the database connection key and target to be passed forward by the - // constructor. - $info = self::$databaseInfo[$key][$target]; - $info['key'] = $key; - $info['target'] = $target; - $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection"; - $new_connection = new $driver_class($info); + $new_connection = new $driver_class(self::$databaseInfo[$key][$target]); $new_connection->setTarget($target); $new_connection->setKey($key); @@ -411,12 +405,13 @@ public static function closeConnection($target = NULL, $key = NULL) { // To close a connection, it needs to be set to NULL and removed from the // static variable. if (isset($target)) { + self::$connections[$key][$target]->destroy(); self::$connections[$key][$target] = NULL; unset(self::$connections[$key][$target]); } else { foreach (self::$connections[$key] as $target => $connection) { - $connection = NULL; + self::closeConnection($target, $key); } unset(self::$connections[$key]); } diff --git a/core/lib/Drupal/Core/Database/Query/Query.php b/core/lib/Drupal/Core/Database/Query/Query.php index 95ab4b9..ab88fdc 100644 --- a/core/lib/Drupal/Core/Database/Query/Query.php +++ b/core/lib/Drupal/Core/Database/Query/Query.php @@ -75,6 +75,7 @@ public function __construct(Connection $connection, $options) { $this->uniqueIdentifier = uniqid('', TRUE); $this->connection = $connection; + $this->connection->addClassReference($this); $this->connectionKey = $this->connection->getKey(); $this->connectionTarget = $this->connection->getTarget(); diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index 2d3159c..343a4f7 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -230,7 +230,7 @@ public function compile(Connection $connection, PlaceholderInterface $queryPlace $this->having->compile($connection, $queryPlaceholder); foreach ($this->tables as $table) { - // If this table is a subquery, compile it recursively. + // If this table is a subquery, compile it recursively. (reference?) if ($table['table'] instanceof SelectInterface) { $table['table']->compile($connection, $queryPlaceholder); } diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php index 2f27d1b..b413c4f 100644 --- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php +++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php @@ -42,6 +42,7 @@ public function __construct(SelectInterface $query, Connection $connection) { $this->uniqueIdentifier = uniqid('', TRUE); $this->query = $query; $this->connection = $connection; +// $this->connection->addClassReference($this); } /** diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 7618a7c..84495ce 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -193,6 +193,7 @@ public function __construct($connection) { $this->uniqueIdentifier = uniqid('', TRUE); $this->connection = $connection; + $this->connection->addClassReference($this); } /** diff --git a/core/lib/Drupal/Core/Database/Statement.php b/core/lib/Drupal/Core/Database/Statement.php index 9972679..21eaee9 100644 --- a/core/lib/Drupal/Core/Database/Statement.php +++ b/core/lib/Drupal/Core/Database/Statement.php @@ -24,20 +24,6 @@ class Statement extends PDOStatement implements StatementInterface { /** - * The database connection key for this statement. - * - * @var string - */ - protected $key; - - /** - * The database connection target for this statement. - * - * @var string - */ - protected $target; - - /** * Reference to the database connection object for this statement. * * The name $dbh is inherited from PDOStatement. @@ -46,9 +32,9 @@ class Statement extends PDOStatement implements StatementInterface { */ public $dbh; - protected function __construct($key, $target) { - $this->key = $key; - $this->target = $target; + protected function __construct($dbh) { + $this->dbh = $dbh; + $this->dbh->addClassReference($this); $this->setFetchMode(PDO::FETCH_OBJ); } @@ -65,7 +51,7 @@ public function execute($args = array(), $options = array()) { } } - $logger = Database::getConnection($this->target, $this->key)->getLogger(); + $logger = $this->dbh->getLogger(); if (!empty($logger)) { $query_start = microtime(TRUE); } diff --git a/core/lib/Drupal/Core/Database/StatementPrefetch.php b/core/lib/Drupal/Core/Database/StatementPrefetch.php index 18dd582..e65dc43 100644 --- a/core/lib/Drupal/Core/Database/StatementPrefetch.php +++ b/core/lib/Drupal/Core/Database/StatementPrefetch.php @@ -126,6 +126,7 @@ class StatementPrefetch implements Iterator, StatementInterface { public function __construct(Connection $connection, $query, array $driver_options = array()) { $this->dbh = $connection; +// $this->dbh->addClassReference($this); $this->queryString = $query; $this->driverOptions = $driver_options; } diff --git a/core/lib/Drupal/Core/Database/Transaction.php b/core/lib/Drupal/Core/Database/Transaction.php index 10adadb..1790cb3 100644 --- a/core/lib/Drupal/Core/Database/Transaction.php +++ b/core/lib/Drupal/Core/Database/Transaction.php @@ -50,8 +50,9 @@ class Transaction { */ protected $name; - public function __construct(Connection &$connection, $name = NULL) { - $this->connection = &$connection; + public function __construct(Connection $connection, $name = NULL) { + $this->connection = $connection; +// $this->connection->addClassReference($this); // If there is no transaction depth, then no transaction has started. Name // the transaction 'drupal_transaction'. if (!$depth = $connection->transactionDepth()) { diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php index c661e98..cd72f26 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php @@ -15,6 +15,10 @@ */ class ConnectionUnitTest extends UnitTestBase { + protected $key; + protected $target; + protected $originalCount; + public static function getInfo() { return array( 'name' => 'Connection unit tests', @@ -23,40 +27,145 @@ public static function getInfo() { ); } - /** - * Tests Database::closeConnection(). - */ - function testClose() { + function setUp() { + parent::setUp(); + + $this->key = 'default'; + $this->originalTarget = 'default'; + $this->target = 'DatabaseConnectionUnitTest'; + // Retrieve current/original amount of connections. - $result = Database::getConnection()->query('SHOW PROCESSLIST')->fetchCol(); - $original_count = count($result); + $this->originalCount = $this->countConnections(); + } - // Verify that retrieving the amount again does not increase it. - $result = Database::getConnection()->query('SHOW PROCESSLIST')->fetchCol(); - $this->assertIdentical(count($result), $original_count); + function tearDown() { + parent::tearDown(); + } + + protected function countConnections() { + return count(Database::getConnection()->query('SHOW PROCESSLIST')->fetchCol()); + } + protected function addConnection() { // Add a new target to the connection, by cloning the current connection. - $connection_info = Database::getConnectionInfo('default'); - $target = 'DatabaseConnectionUnitTest'; - Database::addConnectionInfo('default', $target, $connection_info['default']); + $connection_info = Database::getConnectionInfo($this->key); + Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]); // Verify that the new target exists. - $info = Database::getConnectionInfo('default'); - $this->assertIdentical($info[$target], $connection_info['default']); + $info = Database::getConnectionInfo($this->key); + // Note: Custom assertion message to not expose database credentials. + $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.'); + } + + function testNoop() { + // Verify that retrieving the amount again does not increase it. + $this->assertIdentical($this->countConnections(), $this->originalCount); + } + + /** + * Tests Database::closeConnection(). + */ + function testOpenClose() { + // Add and open a new connection. + $this->addConnection(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertIdentical($this->countConnections(), $this->originalCount + 1); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertIdentical($this->countConnections(), $this->originalCount); + } + + /** + * Tests Database::closeConnection(). + */ + function testOpenQueryClose() { + // Add and open a new connection. + $this->addConnection(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertIdentical($this->countConnections(), $this->originalCount + 1); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES'); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertIdentical($this->countConnections(), $this->originalCount); + } + + /** + * Tests Database::closeConnection(). + */ + function testOpenQueryPrefetchClose() { + // Add and open a new connection. + $this->addConnection(); + Database::getConnection($this->target, $this->key); - // Open the new connection. - Database::getConnection($target, 'default'); + // Verify that there is a new connection. + $this->assertIdentical($this->countConnections(), $this->originalCount + 1); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol(); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertIdentical($this->countConnections(), $this->originalCount); + } + + /** + * Tests Database::closeConnection(). + */ + function testOpenSelectQueryClose() { + // Add and open a new connection. + $this->addConnection(); + Database::getConnection($this->target, $this->key); // Verify that there is a new connection. - $result = Database::getConnection()->query('SHOW PROCESSLIST')->fetchCol(); - $this->assertIdentical(count($result), $original_count + 1); + $this->assertIdentical($this->countConnections(), $this->originalCount + 1); + + // Create a table. + $name = 'foo'; + Database::getConnection($this->target, $this->key)->schema()->createTable($name, array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + ), + ), + )); + + // Execute a query. + Database::getConnection($this->target, $this->key)->select('foo', 'f') + ->fields('f', array('name')) + ->execute() + ->fetchAll(); + + // Drop the table. + Database::getConnection($this->target, $this->key)->schema()->dropTable($name); // Close the connection. - Database::closeConnection($target, 'default'); + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); // Verify that we are back to the original connection count. - $result = Database::getConnection()->query('SHOW PROCESSLIST')->fetchCol(); - $this->assertIdentical(count($result), $original_count); + $this->assertIdentical($this->countConnections(), $this->originalCount); } }