Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.145.2.6 diff -u -p -r1.145.2.6 bootstrap.inc --- includes/bootstrap.inc 26 Jul 2007 19:16:45 -0000 1.145.2.6 +++ includes/bootstrap.inc 5 Aug 2007 16:44:47 -0000 @@ -234,7 +234,7 @@ function drupal_unset_globals() { * session name correctly. */ function conf_init() { - global $base_url, $base_path, $base_root; + global $db_url, $db_slave_url, $db_prefix, $base_url, $base_path, $base_root, $conf, $installed_profile; // Export the following settings.php variables to the global namespace global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile; @@ -382,14 +382,14 @@ function drupal_get_filename($type, $nam function variable_init($conf = array()) { // NOTE: caching the variables improves performance by 20% when serving cached pages. if ($cached = cache_get('variables', 'cache')) { - $variables = unserialize($cached->data); + $variables = $cached->data; } else { $result = db_query('SELECT * FROM {variable}'); while ($variable = db_fetch_object($result)) { $variables[$variable->name] = unserialize($variable->value); } - cache_set('variables', 'cache', serialize($variables)); + cache_set('variables', 'cache', $variables); } foreach ($conf as $name => $value) { @@ -427,14 +427,13 @@ function variable_get($name, $default) { function variable_set($name, $value) { global $conf; - db_lock_table('variable'); - db_query("DELETE FROM {variable} WHERE name = '%s'", $name); - db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value)); - db_unlock_tables(); - - cache_clear_all('variables', 'cache'); + db_query_replace( + array("DELETE FROM {variable} WHERE name = '%s'", $name), + array("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value)) + ); $conf[$name] = $value; + cache_set('variables', 'cache', $conf); } /** Index: includes/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.inc,v retrieving revision 1.62.2.4 diff -u -p -r1.62.2.4 database.inc --- includes/database.inc 12 Jul 2007 06:25:47 -0000 1.62.2.4 +++ includes/database.inc 5 Aug 2007 16:44:47 -0000 @@ -99,16 +99,35 @@ function db_prefix_tables($sql) { * @return the name of the previously active database or FALSE if non was found. */ function db_set_active($name = 'default') { - global $db_url, $db_type, $active_db; - static $db_conns; + global $db_url, $db_slave_url, $db_type, $active_db, $active_slave_db; + static $db_conns, $db_slave_conns; if (!isset($db_conns[$name])) { - // Initiate a new connection, using the named DB URL specified. + // If the $name doesn't have a corresponding URL, use the default. + if (!isset($db_url[$name])) { + $name = 'default'; + } + + // Initiate a new connection, using the specifed DB URL. if (is_array($db_url)) { - $connect_url = array_key_exists($name, $db_url) ? $db_url[$name] : $db_url['default']; + $connect_url = $db_url[$name]; + if (is_array($db_slave_url[$name])) { + $slave_index = mt_rand(0, count($db_slave_url[$name])); + $slave_connect_url = $db_slave_url[$name][$slave_index]; + } + else { + $slave_connect_url = $db_slave_url[$name]; + } } else { $connect_url = $db_url; + if (is_array($db_slave_url)) { + $slave_index = mt_rand(0, count($db_slave_url) - 1); + $slave_connect_url = $db_slave_url[$slave_index]; + } + else { + $slave_connect_url = $db_slave_url; + } } $db_type = substr($connect_url, 0, strpos($connect_url, '://')); @@ -126,11 +145,20 @@ function db_set_active($name = 'default' } $db_conns[$name] = db_connect($connect_url); + if (!empty($slave_connect_url)) { + $db_slave_conns[$name] = db_connect($slave_connect_url); + } } $previous_db = $active_db; - // Set the active connection. + // Set the active connections. $active_db = $db_conns[$name]; + if (isset($db_slave_conns[$name])) { + $active_slave_db = $db_slave_conns[$name]; + } + else { + unset($active_slave_db); + } return array_search($previous_db, $db_conns); } @@ -201,6 +229,42 @@ function db_query($query) { } /** + * Runs a basic query in the active slave database. + * + * User-supplied arguments to the query should be passed in as separate + * parameters so that they can be properly escaped to avoid SQL injection + * attacks. + * + * @param $query + * A string containing an SQL query. + * @param ... + * A variable number of arguments which are substituted into the query + * using printf() syntax. Instead of a variable number of query arguments, + * you may also pass a single array containing the query arguments. + + * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose + * in '') and %%. + * + * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, + * and TRUE values to decimal 1. + * + * @return + * A database query result resource, or FALSE if the query was not + * executed correctly. + */ +function db_query_slave($query) { + $args = func_get_args(); + array_shift($args); + $query = db_prefix_tables($query); + if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax + $args = $args[0]; + } + _db_query_callback($args, TRUE); + $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); + return _db_query($query, FALSE, TRUE); +} + +/** * Helper function for db_rewrite_sql. * * Collects JOIN and WHERE statements via hook_db_rewrite_sql(). Index: includes/database.mysql.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.mysql.inc,v retrieving revision 1.66.2.1 diff -u -p -r1.66.2.1 database.mysql.inc --- includes/database.mysql.inc 22 Jan 2007 02:20:50 -0000 1.66.2.1 +++ includes/database.mysql.inc 5 Aug 2007 16:44:47 -0000 @@ -142,23 +142,30 @@ function db_connect($url) { /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $queries; +function _db_query($query, $debug = 0, $slave = FALSE) { + global $active_db, $active_slave_db, $queries; if (variable_get('dev_query', 0)) { list($usec, $sec) = explode(' ', microtime()); $timer = (float)$usec + (float)$sec; } - $result = mysql_query($query, $active_db); - + $sent_to_slave = FALSE; + if (isset($active_slave_db) && $slave) { + $result = mysql_query($query, $active_slave_db); + $sent_to_slave = TRUE; + } + else { + $result = mysql_query($query, $active_db); + } + if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); $query = $bt[2]['function'] . "\n" . $query; list($usec, $sec) = explode(' ', microtime()); $stop = (float)$usec + (float)$sec; $diff = $stop - $timer; - $queries[] = array($query, $diff); + $queries[] = array($query, $diff, $sent_to_slave); } if ($debug) { @@ -321,6 +328,50 @@ function db_query_range($query) { } /** + * Runs a limited-range query in the active slave database. + * + * Use this as a substitute for db_query_slave() when a subset of the query is to be + * returned. + * User-supplied arguments to the query should be passed in as separate parameters + * so that they can be properly escaped to avoid SQL injection attacks. + * + * @param $query + * A string containing an SQL query. + * @param ... + * A variable number of arguments which are substituted into the query + * using printf() syntax. The query arguments can be enclosed in one + * array instead. + * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose + * in '') and %%. + * + * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, + * and TRUE values to decimal 1. + * + * @param $from + * The first result row to return. + * @param $count + * The maximum number of result rows to return. + * @return + * A database query result resource, or FALSE if the query was not executed + * correctly. + */ +function db_query_range_slave($query) { + $args = func_get_args(); + $count = array_pop($args); + $from = array_pop($args); + array_shift($args); + + $query = db_prefix_tables($query); + if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax + $args = $args[0]; + } + _db_query_callback($args, TRUE); + $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); + $query .= ' LIMIT '. (int)$from .', '. (int)$count; + return _db_query($query, FALSE, TRUE); +} + +/** * Runs a SELECT query and stores its results in a temporary table. * * Use this as a substitute for db_query() when the results need to stored @@ -358,13 +409,13 @@ function db_query_temporary($query) { $tablename = array_pop($args); array_shift($args); - $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' SELECT', db_prefix_tables($query)); + $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query)); if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax $args = $args[0]; } _db_query_callback($args, TRUE); $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - return _db_query($query); + return _db_query($query, FALSE, TRUE); } /** @@ -439,7 +490,20 @@ function db_distinct_field($table, $fiel } /** + * Perform a replace query. + * + * @param $delete + * An array, where the first value is a query which deletes one row, + * subsequent values are arguments. Same syntax as of db_query. + * @param + * An array, where the first value is a query which inserts the new row, + * subsequent values are arguments. Same syntax as of db_query. + */ +function db_query_replace($delete, $insert) { + $insert_query = array_shift($insert); + db_query(preg_replace('/^INSERT /', 'REPLACE ', $insert_query), $insert); +} + +/** * @} End of "ingroup database". */ - - Index: includes/database.mysqli.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.mysqli.inc,v retrieving revision 1.32 diff -u -p -r1.32 database.mysqli.inc --- includes/database.mysqli.inc 27 Dec 2006 22:50:09 -0000 1.32 +++ includes/database.mysqli.inc 5 Aug 2007 16:44:47 -0000 @@ -121,15 +121,22 @@ function db_connect($url) { /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $queries; +function _db_query($query, $debug = 0, $slave = FALSE) { + global $active_db, $active_slave_db, $queries; if (variable_get('dev_query', 0)) { list($usec, $sec) = explode(' ', microtime()); $timer = (float)$usec + (float)$sec; } - $result = mysqli_query($active_db, $query); + $sent_to_slave = FALSE; + if (isset($active_slave_db) && $slave) { + $result = mysqli_query($active_slave_db, $query); + $sent_to_slave = TRUE; + } + else { + $result = mysqli_query($active_db, $query); + } if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); @@ -137,7 +144,7 @@ function _db_query($query, $debug = 0) { list($usec, $sec) = explode(' ', microtime()); $stop = (float)$usec + (float)$sec; $diff = $stop - $timer; - $queries[] = array($query, $diff); + $queries[] = array($query, $diff, $sent_to_slave); } if ($debug) { @@ -301,6 +308,50 @@ function db_query_range($query) { } /** + * Runs a limited-range query in the active slave database. + * + * Use this as a substitute for db_query_slave() when a subset of the query is to be + * returned. + * User-supplied arguments to the query should be passed in as separate parameters + * so that they can be properly escaped to avoid SQL injection attacks. + * + * @param $query + * A string containing an SQL query. + * @param ... + * A variable number of arguments which are substituted into the query + * using printf() syntax. The query arguments can be enclosed in one + * array instead. + * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose + * in '') and %%. + * + * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, + * and TRUE values to decimal 1. + * + * @param $from + * The first result row to return. + * @param $count + * The maximum number of result rows to return. + * @return + * A database query result resource, or FALSE if the query was not executed + * correctly. + */ +function db_query_range_slave($query) { + $args = func_get_args(); + $count = array_pop($args); + $from = array_pop($args); + array_shift($args); + + $query = db_prefix_tables($query); + if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax + $args = $args[0]; + } + _db_query_callback($args, TRUE); + $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); + $query .= ' LIMIT '. (int)$from .', '. (int)$count; + return _db_query($query, FALSE, TRUE); +} + +/** * Runs a SELECT query and stores its results in a temporary table. * * Use this as a substitute for db_query() when the results need to stored @@ -419,6 +470,21 @@ function db_distinct_field($table, $fiel } /** + * Perform a replace query. + * + * @param $delete + * An array, where the first value is a query which deletes one row, + * subsequent values are arguments. Same syntax as of db_query. + * @param + * An array, where the first value is a query which inserts the new row, + * subsequent values are arguments. Same syntax as of db_query. + */ +function db_query_replace($delete, $insert) { + $insert_query = array_shift($insert); + db_query(preg_replace('/^INSERT /', 'REPLACE ', $insert_query), $insert); +} + +/** * @} End of "ingroup database". */ Index: includes/database.pgsql.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.pgsql.inc,v retrieving revision 1.43 diff -u -p -r1.43 database.pgsql.inc --- includes/database.pgsql.inc 27 Dec 2006 22:13:56 -0000 1.43 +++ includes/database.pgsql.inc 5 Aug 2007 16:44:47 -0000 @@ -114,15 +114,22 @@ function db_connect($url) { /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $last_result, $queries; +function _db_query($query, $debug = 0, $slave = FALSE) { + global $active_db, $active_slave_db, $last_result, $queries; if (variable_get('dev_query', 0)) { list($usec, $sec) = explode(' ', microtime()); $timer = (float)$usec + (float)$sec; } - $last_result = pg_query($active_db, $query); + $sent_to_slave = FALSE; + if (isset($active_slave_db) && $slave) { + $last_result = pg_query($active_slave_db, $query); + $sent_to_slave = TRUE; + } + else { + $last_result = pg_query($active_db, $query); + } if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); @@ -130,7 +137,7 @@ function _db_query($query, $debug = 0) { list($usec, $sec) = explode(' ', microtime()); $stop = (float)$usec + (float)$sec; $diff = $stop - $timer; - $queries[] = array($query, $diff); + $queries[] = array($query, $diff, $sent_to_slave); } if ($debug) { @@ -291,6 +298,51 @@ function db_query_range($query) { } /** + * Runs a limited-range query in the active slave database. + * + * Use this as a substitute for db_query_slave() when a subset of the query + * is to be returned. + * User-supplied arguments to the query should be passed in as separate + * parameters so that they can be properly escaped to avoid SQL injection + * attacks. + * + * @param $query + * A string containing an SQL query. + * @param ... + * A variable number of arguments which are substituted into the query + * using printf() syntax. Instead of a variable number of query arguments, + * you may also pass a single array containing the query arguments. + * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose + * in '') and %%. + * + * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, + * and TRUE values to decimal 1. + * + * @param $from + * The first result row to return. + * @param $count + * The maximum number of result rows to return. + * @return + * A database query result resource, or FALSE if the query was not executed + * correctly. + */ +function db_query_range_slave($query) { + $args = func_get_args(); + $count = array_pop($args); + $from = array_pop($args); + array_shift($args); + + $query = db_prefix_tables($query); + if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax + $args = $args[0]; + } + _db_query_callback($args, TRUE); + $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); + $query .= ' LIMIT '. (int)$count .' OFFSET '. (int)$from; + return _db_query($query, FALSE, TRUE); +} + +/** * Runs a SELECT query and stores its results in a temporary table. * * Use this as a substitute for db_query() when the results need to stored Index: includes/pager.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/pager.inc,v retrieving revision 1.59 diff -u -p -r1.59 pager.inc --- includes/pager.inc 15 Oct 2006 19:57:05 -0000 1.59 +++ includes/pager.inc 5 Aug 2007 16:44:47 -0000 @@ -69,10 +69,10 @@ function pager_query($query, $limit = 10 $pager_page_array = explode(',', $page); // We calculate the total of pages as ceil(items / limit). - $pager_total_items[$element] = db_result(db_query($count_query, $args)); + $pager_total_items[$element] = db_result(db_query_slave($count_query, $args)); $pager_total[$element] = ceil($pager_total_items[$element] / $limit); $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1)); - return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit); + return db_query_range_slave($query, $args, $pager_page_array[$element] * $limit, $limit); } /** Index: modules/search/search.module =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.module,v retrieving revision 1.209.2.5 diff -u -p -r1.209.2.5 search.module --- modules/search/search.module 26 Jul 2007 19:16:48 -0000 1.209.2.5 +++ modules/search/search.module 5 Aug 2007 16:44:47 -0000 @@ -852,7 +852,7 @@ function do_search($keywords, $type, $jo $result = db_query_temporary("SELECT i.type, i.sid, SUM(i.score * t.count) AS relevance, COUNT(*) AS matches FROM {search_index} i INNER JOIN {search_total} t ON i.word = t.word $join1 WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d", $arguments, 'temp_search_sids'); // Calculate maximum relevance, to normalize it - $normalize = db_result(db_query('SELECT MAX(relevance) FROM temp_search_sids')); + $normalize = db_result(db_query_slave('SELECT MAX(relevance) FROM temp_search_sids')); if (!$normalize) { return array(); } @@ -862,7 +862,7 @@ function do_search($keywords, $type, $jo $conditions = '('. $query[0] .')'; $arguments = array_merge($arguments2, $query[1]); $result = db_query_temporary("SELECT i.type, i.sid, $select2 FROM temp_search_sids i INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type $join2 WHERE $conditions $sort_parameters", $arguments, 'temp_search_results'); - if (($count = db_result(db_query('SELECT COUNT(*) FROM temp_search_results'))) == 0) { + if (($count = db_result(db_query_slave('SELECT COUNT(*) FROM temp_search_results'))) == 0) { return array(); } $count_query = "SELECT $count";