Index: includes/database/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/database.inc,v retrieving revision 1.20 diff -u -p -r1.20 database.inc --- includes/database/database.inc 1 Nov 2008 21:21:35 -0000 1.20 +++ includes/database/database.inc 30 Oct 2008 17:55:35 -0000 @@ -155,6 +155,11 @@ abstract class DatabaseConnection extend */ protected $logger = NULL; + /** + * The prefix used by that database connection. + */ + protected $prefix = NULL; + function __construct($dsn, $username, $password, $driver_options = array()) { // Because the other methods don't seem to work right. $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; @@ -236,26 +241,24 @@ abstract class DatabaseConnection extend * The properly-prefixed string. */ protected function prefixTables($sql) { - global $db_prefix; - - if (is_array($db_prefix)) { - if (array_key_exists('default', $db_prefix)) { - $tmp = $db_prefix; + if (is_array($this->prefix)) { + if (array_key_exists('default', $this->prefix)) { + $tmp = $this->prefix; unset($tmp['default']); foreach ($tmp as $key => $val) { $sql = strtr($sql, array('{' . $key . '}' => $val . $key)); } - return strtr($sql, array('{' => $db_prefix['default'] , '}' => '')); + return strtr($sql, array('{' => $this->prefix['default'], '}' => '')); } else { - foreach ($db_prefix as $key => $val) { + foreach ($this->prefix as $key => $val) { $sql = strtr($sql, array('{' . $key . '}' => $val . $key)); } return strtr($sql, array('{' => '' , '}' => '')); } } else { - return strtr($sql, array('{' => $db_prefix , '}' => '')); + return strtr($sql, array('{' => $this->prefix, '}' => '')); } } @@ -331,6 +334,28 @@ abstract class DatabaseConnection extend } /** + * Set a new prefix for this connection. + * + * @param $prefix + * The new prefix to use for this connection. Either a string or an array + * keyed by table name. + */ + public function setPrefix($prefix) { + $this->prefix = $prefix; + } + + /** + * Get the current prefix for this connection. + * + * @return + * The current prefix for this connection. + * Either a string or an array keyed by table name. + */ + public function getPrefix() { + return $this->prefix; + } + + /** * Create the appropriate sequence name for a given table and serial field. * * This information is exposed to all database drivers, although it is only @@ -951,7 +976,40 @@ abstract class Database { if (!empty(self::$databaseInfo[$key])) { return self::$databaseInfo[$key]; } + } + /** + * Rename a connection and its corresponding connection information. + * + * @param $old_key + * The old connection key. + * @param $new_key + * The new connection key. + */ + final public static function renameConnection($old_key, $new_key) { + if (empty(self::$databaseInfo)) { + self::parseConnectionInfo(); + } + + if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) { + self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key]; + unset(self::$databaseInfo[$old_key]); + if (isset(self::$connections[$old_key])) { + self::$connections[$new_key] = self::$connections[$old_key]; + unset(self::$connections[$old_key]); + } + } + } + + /** + * Remove a connection and its corresponding connection information. + * + * @param $key + * The connection key. + */ + final public static function removeConnection($key) { + unset(self::$databaseInfo[$key]); + unset(self::$connections[$key]); } /** @@ -967,7 +1025,6 @@ abstract class Database { * The name of the target that was actually opened. */ final protected static function openConnection($key, $target) { - global $db_prefix; if (empty(self::$databaseInfo)) { self::parseConnectionInfo(); @@ -1002,12 +1059,6 @@ abstract class Database { self::$connections[$key][$target]->setLogger(self::$logs[$key]); } - // We need to pass around the simpletest database prefix in the request - // and we put that in the user_agent header. - if (preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) { - $db_prefix .= $_SERVER['HTTP_USER_AGENT']; - } - // Return the target that was actually opened in case the requested one // didn't exist. return $target; Index: includes/database/schema.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/schema.inc,v retrieving revision 1.4 diff -u -p -r1.4 schema.inc --- includes/database/schema.inc 31 Oct 2008 15:46:16 -0000 1.4 +++ includes/database/schema.inc 30 Oct 2008 17:54:56 -0000 @@ -415,7 +415,6 @@ abstract class DatabaseSchema { * Array, both the keys and the values are the matching tables. */ public function findTables($table_expression) { - global $db_prefix; $info = Database::getConnectionInfo(); $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = :database AND table_name LIKE :table_name", array( ':database' => $info['default']['database'], Index: includes/database/mysql/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/mysql/database.inc,v retrieving revision 1.4 diff -u -p -r1.4 database.inc --- includes/database/mysql/database.inc 25 Oct 2008 02:03:55 -0000 1.4 +++ includes/database/mysql/database.inc 30 Oct 2008 17:54:56 -0000 @@ -23,6 +23,8 @@ class DatabaseConnection_mysql extends D ); $this->transactionSupport = $connection_options['transactions']; + $this->prefix = isset($connection_options['prefix']) ? $connection_options['prefix'] : ''; + $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . $connection_options['port'] . ';dbname=' . $connection_options['database']; parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( // So we don't have to mess around with cursors and unbuffered queries by default. Index: includes/database/pgsql/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/pgsql/database.inc,v retrieving revision 1.5 diff -u -p -r1.5 database.inc --- includes/database/pgsql/database.inc 31 Oct 2008 11:24:04 -0000 1.5 +++ includes/database/pgsql/database.inc 30 Oct 2008 17:54:56 -0000 @@ -22,6 +22,8 @@ class DatabaseConnection_pgsql extends D ); $this->transactionSupport = $connection_options['transactions']; + $this->prefix = isset($connection_options['prefix']) ? $connection_options['prefix'] : ''; + $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database']; if (!empty($connection_options['port'])) { $dsn .= ' port=' . $connection_options['port']; Index: sites/default/default.settings.php =================================================================== RCS file: /cvs/drupal/drupal/sites/default/default.settings.php,v retrieving revision 1.15 diff -u -p -r1.15 default.settings.php --- sites/default/default.settings.php 6 Oct 2008 10:54:15 -0000 1.15 +++ sites/default/default.settings.php 30 Oct 2008 17:54:56 -0000 @@ -105,30 +105,31 @@ * 'username' => 'username', * 'password' => 'password', * 'host' => 'localhost', + * 'prefix' => '', * ); * * You can optionally set prefixes for some or all database table names - * by using the $db_prefix setting. If a prefix is specified, the table + * by using the 'prefix' setting. If a prefix is specified, the table * name will be prepended with its value. Be sure to use valid database * characters only, usually alphanumeric and underscore. If no prefixes * are desired, leave it as an empty string ''. * - * To have all database names prefixed, set $db_prefix as a string: + * To have all database names prefixed, set 'prefix' as a string: * - * $db_prefix = 'main_'; + * 'prefix' => 'main_', * - * To provide prefixes for specific tables, set $db_prefix as an array. + * To provide prefixes for specific tables, set 'prefix' as an array. * The array's keys are the table names and the values are the prefixes. * The 'default' element holds the prefix for any tables not specified * elsewhere in the array. Example: * - * $db_prefix = array( + * 'prefix' = array( * 'default' => 'main_', * 'users' => 'shared_', * 'sessions' => 'shared_', * 'role' => 'shared_', * 'authmap' => 'shared_', - * ); + * ), * * Database configuration format: * $databases['default']['default'] = array( @@ -151,7 +152,6 @@ * ); */ $databases = array(); -$db_prefix = ''; /** * Access control for update.php script Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.245 diff -u -p -r1.245 bootstrap.inc --- includes/bootstrap.inc 2 Nov 2008 10:56:35 -0000 1.245 +++ includes/bootstrap.inc 30 Oct 2008 17:54:56 -0000 @@ -403,7 +403,7 @@ function conf_init() { global $base_url, $base_path, $base_root; // Export the following settings.php variables to the global namespace - global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; + global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access; $conf = array(); if (!drupal_valid_http_host()) { @@ -479,6 +479,11 @@ function conf_init() { ini_set('session.cookie_domain', $cookie_domain); } session_name('SESS' . md5($session_name)); + + // If running in a simpletest context, forcefully change the database prefix. + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) { + $databases['default']['default']['prefix'] = $_SERVER['HTTP_USER_AGENT']; + } } /** Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.815 diff -u -p -r1.815 common.inc --- includes/common.inc 1 Nov 2008 21:21:34 -0000 1.815 +++ includes/common.inc 30 Oct 2008 17:54:56 -0000 @@ -412,7 +412,6 @@ function drupal_access_denied() { * data and redirect status. */ function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) { - global $db_prefix; static $self_test = FALSE; $result = new stdClass(); // Try to clear the drupal_http_request_fails variable if it's set. We @@ -497,8 +496,8 @@ function drupal_http_request($url, $head // user-agent is used to ensure that multiple testing sessions running at the // same time won't interfere with each other as they would if the database // prefix were stored statically in a file or database variable. - if (preg_match("/simpletest\d+/", $db_prefix, $matches)) { - $headers['User-Agent'] = $matches[0]; + if (preg_match("/^simpletest\d+/", $prefix = Database::getActiveConnection()->getPrefix())) { + $headers['User-Agent'] = $prefix; } foreach ($headers as $header => $value) { Index: install.php =================================================================== RCS file: /cvs/drupal/drupal/install.php,v retrieving revision 1.138 diff -u -p -r1.138 install.php --- install.php 29 Oct 2008 10:08:51 -0000 1.138 +++ install.php 30 Oct 2008 17:54:56 -0000 @@ -176,7 +176,7 @@ function install_verify_drupal() { * Verify existing settings.php */ function install_verify_settings() { - global $db_prefix, $databases; + global $databases; // Verify existing settings (if any). if (!empty($databases)) { @@ -199,7 +199,7 @@ function install_verify_settings() { * Configure and rewrite settings.php. */ function install_change_settings($profile = 'default', $install_locale = '') { - global $databases, $db_prefix; + global $databases; $conf_path = './' . conf_path(FALSE, TRUE); $settings_file = $conf_path . '/settings.php'; @@ -313,14 +313,14 @@ function install_settings_form(&$form_st ); // Table prefix - $db_prefix = ($profile == 'default') ? 'drupal_' : $profile . '_'; - $form['advanced_options']['db_prefix'] = array( + $prefix = ($profile == 'default') ? 'drupal_' : $profile . '_'; + $form['advanced_options']['prefix'] = array( '#type' => 'textfield', '#title' => st('Table prefix'), '#default_value' => '', '#size' => 45, '#maxlength' => 45, - '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_name(), '%prefix' => $db_prefix)), + '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_name(), '%prefix' => $prefix)), ); $form['save'] = array( @@ -341,7 +341,6 @@ function install_settings_form(&$form_st * Form API validate for install_settings form. */ function install_settings_form_validate($form, &$form_state) { - global $db_url; _install_settings_form_validate($form_state['values'], $form_state['values']['settings_file'], $form_state, $form); } @@ -351,12 +350,12 @@ function install_settings_form_validate( function _install_settings_form_validate($database, $settings_file, &$form_state, $form = NULL) { global $databases; // Verify the table prefix - if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['dprefix'])) { - form_set_error('db_prefix', st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $db_prefix)), 'error'); + if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) { + form_set_error('prefix', st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $prefix)), 'error'); } if (!empty($database['port']) && !is_numeric($database['port'])) { - form_set_error('db_port', st('Database port must be a number.')); + form_set_error('port', st('Database port must be a number.')); } // Check database type @@ -390,16 +389,12 @@ function _install_settings_form_validate function install_settings_form_submit($form, &$form_state) { global $profile, $install_locale; - $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port'))); + $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port', 'prefix'))); // Update global settings array and save $settings['databases'] = array( 'value' => array('default' => array('default' => $database)), 'required' => TRUE, ); - $settings['db_prefix'] = array( - 'value' => $form_state['values']['db_prefix'], - 'required' => TRUE, - ); drupal_rewrite_settings($settings); // Continue to install profile step Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.53 diff -u -p -r1.53 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 1 Nov 2008 21:21:35 -0000 1.53 +++ modules/simpletest/drupal_web_test_case.php 30 Oct 2008 17:54:56 -0000 @@ -17,7 +17,7 @@ class DrupalWebTestCase { // Overwrite this any time to supply cURL options as necessary, // DrupalTestCase itself never sets this but always obeys whats set. protected $curl_options = array(); - protected $db_prefix_original; + protected $prefix; protected $original_file_directory; var $_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0); @@ -51,8 +51,6 @@ class DrupalWebTestCase { * is the caller function itself. */ protected function _assert($status, $message = '', $group = 'Other', $caller = NULL) { - global $db_prefix; - // Convert boolean status to string status. if (is_bool($status)) { $status = $status ? 'pass' : 'fail'; @@ -66,10 +64,6 @@ class DrupalWebTestCase { $caller = $this->getAssertionCall(); } - // Switch to non-testing database to store results in. - $current_db_prefix = $db_prefix; - $db_prefix = $this->db_prefix_original; - // Creation assertion array that can be displayed while tests are running. $this->_assertions[] = $assertion = array( 'test_id' => $this->test_id, @@ -83,10 +77,10 @@ class DrupalWebTestCase { ); // Store assertion for display after the test has completed. - db_insert('simpletest')->fields($assertion)->execute(); - - // Return to testing prefix. - $db_prefix = $current_db_prefix; + Database::getConnection('simpletest_original_default') + ->insert('simpletest') + ->fields($assertion) + ->execute(); return $status; } @@ -682,14 +676,16 @@ class DrupalWebTestCase { * List of modules to enable for the duration of the test. */ function setUp() { - global $db_prefix; + // Generate temporary prefixed database to ensure that tests have a clean starting point. + $this->prefix = 'simpletest' . mt_rand(1000, 1000000); - // Store necessary current values before switching to prefixed database. - $this->db_prefix_original = $db_prefix; - $clean_url_original = variable_get('clean_url', 0); + // Clone the current connection and add our prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + $connection_info['default']['prefix'] = $this->prefix; + Database::addConnectionInfo('default', 'default', $connection_info['default']); - // Generate temporary prefixed database to ensure that tests have a clean starting point. - $db_prefix = Database::getActiveConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); + $clean_url_original = variable_get('clean_url', 0); include_once DRUPAL_ROOT . '/includes/install.inc'; drupal_install_system(); @@ -723,7 +719,7 @@ class DrupalWebTestCase { // Use temporary files directory with the same prefix as database. $this->original_file_directory = file_directory_path(); - variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix); + variable_set('file_directory_path', file_directory_path() . '/' . $this->prefix); $directory = file_directory_path(); file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory. } @@ -751,8 +747,10 @@ class DrupalWebTestCase { * and reset the database prefix. */ function tearDown() { - global $db_prefix; - if (preg_match('/simpletest\d+/', $db_prefix)) { + db_set_active('default'); + $connection_info = Database::getConnectionInfo(); + + if (preg_match('/simpletest\d+/', $connection_info['default']['prefix'])) { // Delete temporary files directory and reset files directory path. simpletest_clean_temporary_directory(file_directory_path()); variable_set('file_directory_path', $this->original_file_directory); @@ -764,8 +762,9 @@ class DrupalWebTestCase { db_drop_table($ret, $name); } - // Return the database prefix to the original. - $db_prefix = $this->db_prefix_original; + // Get back to the original connection. + Database::removeConnection('default'); + Database::renameConnection('simpletest_original_default', 'default'); // Ensure that the internal logged in variable is reset. $this->_logged_in = FALSE; @@ -789,7 +788,7 @@ class DrupalWebTestCase { * Also, see the description of $curl_options among the properties. */ protected function curlConnect() { - global $base_url, $db_prefix; + global $base_url; if (!isset($this->ch)) { $this->ch = curl_init(); $curl_options = $this->curl_options + array( @@ -800,7 +799,7 @@ class DrupalWebTestCase { CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https:// CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https:// ); - if (preg_match('/simpletest\d+/', $db_prefix, $matches)) { + if (preg_match('/simpletest\d+/', $this->prefix, $matches)) { $curl_options[CURLOPT_USERAGENT] = $matches[0]; } if (!isset($curl_options[CURLOPT_USERPWD]) && ($auth = variable_get('simpletest_httpauth_username', ''))) { Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.26 diff -u -p -r1.26 simpletest.module --- modules/simpletest/simpletest.module 1 Nov 2008 21:21:35 -0000 1.26 +++ modules/simpletest/simpletest.module 30 Oct 2008 17:54:56 -0000 @@ -60,8 +60,6 @@ function simpletest_theme() { * Menu callback for both running tests and listing possible tests */ function simpletest_test_form() { - global $db_prefix, $db_prefix_original; - $form = array(); // List out all tests in groups for selection. @@ -342,7 +340,6 @@ function simpletest_test_form_submit($fo * drupal being the default. */ function simpletest_run_tests($test_list, $reporter = 'drupal') { - global $db_prefix, $db_prefix_original; cache_clear_all(); $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); Index: modules/simpletest/tests/database_test.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v retrieving revision 1.13 diff -u -p -r1.13 database_test.test --- modules/simpletest/tests/database_test.test 25 Oct 2008 04:26:30 -0000 1.13 +++ modules/simpletest/tests/database_test.test 30 Oct 2008 17:54:56 -0000 @@ -32,12 +32,14 @@ class DatabaseTestCase extends DrupalWeb // This ends up being a test for table drop and create, too, which is // nice. + error_log("Just before creating the test schema."); foreach ($schema as $name => $data) { if (db_table_exists($name)) { db_drop_table($ret, $name); } db_create_table($ret, $name, $data); } + error_log("Just after creating the test schema."); foreach ($schema as $name => $data) { $this->assertTrue(db_table_exists($name), t('Table @name created successfully.', array('@name' => $name))); Index: modules/aggregator/aggregator.test =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v retrieving revision 1.12 diff -u -p -r1.12 aggregator.test --- modules/aggregator/aggregator.test 2 Nov 2008 11:17:56 -0000 1.12 +++ modules/aggregator/aggregator.test 30 Oct 2008 17:54:56 -0000 @@ -2,7 +2,7 @@ // $Id: aggregator.test,v 1.12 2008-11-02 11:17:56 dries Exp $ class AggregatorTestCase extends DrupalWebTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $aggregator_prefix = 'simpletest_aggregator_'; /** * Implementation of setUp(). @@ -44,7 +44,7 @@ class AggregatorTestCase extends DrupalW * @return array Feed array. */ function getFeedEditArray() { - $feed_name = $this->randomName(10, self::$prefix); + $feed_name = $this->randomName(10, self::$aggregator_prefix); $feed_url = url(NULL, array('absolute' => TRUE)) . 'rss.xml?feed=' . $feed_name; $edit = array( 'title' => $feed_name, @@ -427,7 +427,7 @@ class RemoveFeedItemTestCase extends Agg } class CategorizeFeedItemTestCase extends AggregatorTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $aggregator_prefix = 'simpletest_aggregator_'; /** * Implementation of getInfo(). */ @@ -445,7 +445,7 @@ class CategorizeFeedItemTestCase extends */ function testCategorizeFeedItem() { // Simulate form submission on "admin/content/aggregator/add/category". - $edit = array('title' => $this->randomName(10, self::$prefix), 'description' => ''); + $edit = array('title' => $this->randomName(10, self::$aggregator_prefix), 'description' => ''); $this->drupalPost('admin/content/aggregator/add/category', $edit, t('Save')); $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), t('The category %title has been added.', array('%title' => $edit['title']))); @@ -480,7 +480,7 @@ class CategorizeFeedItemTestCase extends } class ImportOPMLTestCase extends AggregatorTestCase { - private static $prefix = 'simpletest_aggregator_'; + private static $aggregator_prefix = 'simpletest_aggregator_'; /** * Implementation of getInfo(). @@ -499,7 +499,7 @@ class ImportOPMLTestCase extends Aggrega function openImportForm() { db_delete('aggregator_category')->execute(); - $category = $this->randomName(10, self::$prefix); + $category = $this->randomName(10, self::$aggregator_prefix); db_insert('aggregator_category') ->fields(array( 'cid' => 1, @@ -563,7 +563,7 @@ class ImportOPMLTestCase extends Aggrega db_delete('aggregator_category')->execute(); db_delete('aggregator_category_feed')->execute(); - $category = $this->randomName(10, self::$prefix); + $category = $this->randomName(10, self::$aggregator_prefix); db_insert('aggregator_category') ->fields(array( 'cid' => 1,