Index: database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.inc,v retrieving revision 1.69 diff -u -r1.69 database.inc --- database.inc 25 May 2007 12:46:43 -0000 1.69 +++ database.inc 27 May 2007 07:52:11 -0000 @@ -115,8 +115,8 @@ * @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 (empty($db_url)) { include_once 'includes/install.inc'; @@ -124,12 +124,31 @@ } 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)); + $slave_connect_url = $db_slave_url[$slave_index]; + } + else { + $slave_connect_url = $db_slave_url; + } } $db_type = substr($connect_url, 0, strpos($connect_url, '://')); @@ -147,11 +166,20 @@ } $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); } @@ -222,6 +250,42 @@ } /** + * Runs a basic, read-only 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_read_only($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, 0, TRUE); +} + +/** * Helper function for db_rewrite_sql. * * Collects JOIN and WHERE statements via hook_db_rewrite_sql() Index: database.mysql.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.mysql.inc,v retrieving revision 1.73 diff -u -r1.73 database.mysql.inc --- database.mysql.inc 25 May 2007 21:01:30 -0000 1.73 +++ database.mysql.inc 27 May 2007 07:52:11 -0000 @@ -133,15 +133,20 @@ /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $queries; +function _db_query($query, $debug = 0, $read_only = 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); + if (isset($active_slave_db) && ($read_only || strtoupper(substr($query, 0, 6)) == 'SELECT')) { + $result = mysql_query($active_slave_db, $query); + } + else { + $result = mysql_query($active_db, $query); + } if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); @@ -310,6 +315,50 @@ } /** + * Runs a limited-range, read-only query in the active slave database. + * + * Use this as a substitute for db_query() 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_read_only($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, 0, 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: database.mysqli.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.mysqli.inc,v retrieving revision 1.37 diff -u -r1.37 database.mysqli.inc --- database.mysqli.inc 25 May 2007 21:01:30 -0000 1.37 +++ database.mysqli.inc 27 May 2007 07:52:12 -0000 @@ -124,15 +124,20 @@ /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $queries; +function _db_query($query, $debug = 0, $read_only = 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); + if (isset($active_slave_db) && ($read_only || strtoupper(substr($query, 0, 6)) == 'SELECT')) { + $result = mysqli_query($active_slave_db, $query); + } + else { + $result = mysqli_query($active_db, $query); + } if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); @@ -301,6 +306,51 @@ return _db_query($query); } + +/** + * Runs a limited-range, read-only query in the active slave database. + * + * Use this as a substitute for db_query() 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_read_only($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, 0, TRUE); +} + /** * Runs a SELECT query and stores its results in a temporary table. * Index: database.pgsql.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database.pgsql.inc,v retrieving revision 1.46 diff -u -r1.46 database.pgsql.inc --- database.pgsql.inc 25 May 2007 12:46:43 -0000 1.46 +++ database.pgsql.inc 27 May 2007 07:52:12 -0000 @@ -114,15 +114,20 @@ /** * Helper function for db_query(). */ -function _db_query($query, $debug = 0) { - global $active_db, $last_result, $queries; +function _db_query($query, $debug = 0, $read_only = 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); + if (isset($active_slave_db) && ($read_only || strtoupper(substr($query, 0, 6)) == 'SELECT')) { + $result = pg_query($active_slave_db, $query); + } + else { + $result = pg_query($active_db, $query); + } if (variable_get('dev_query', 0)) { $bt = debug_backtrace(); @@ -291,6 +296,51 @@ } /** + * Runs a limited-range, read-only query in the active slave database. + * + * Use this as a substitute for db_query() 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_read_only($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, 0, 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