Improve selection of replicated database connection:

- Analyze query and prefer dsnr unless a write operation for a table involved has been carried out before
- New config option and setter method to enforce connection mode on table level
pull/135/head
Thomas Bruederli 11 years ago
parent 120db629b0
commit a69f9918cd

@ -41,6 +41,16 @@ $config['db_persistent'] = false;
// you can define specific table (and sequence) names prefix // you can define specific table (and sequence) names prefix
$config['db_prefix'] = ''; $config['db_prefix'] = '';
// Mapping of table names and connections to use for ALL operations.
// This can be used in a setup with replicated databases and a DB master
// where read/write access to cache tables should not go to master.
$config['db_table_dsn'] = array(
// 'cache' => 'r',
// 'cache_index' => 'r',
// 'cache_thread' => 'r',
// 'cache_messages' => 'r',
);
// ---------------------------------- // ----------------------------------
// LOGGING/DEBUGGING // LOGGING/DEBUGGING

@ -31,8 +31,10 @@ class rcube_db
protected $db_dsnr; // DSN for read operations protected $db_dsnr; // DSN for read operations
protected $db_connected = false; // Already connected ? protected $db_connected = false; // Already connected ?
protected $db_mode; // Connection mode protected $db_mode; // Connection mode
protected $db_table_dsn_map = array();
protected $dbh; // Connection handle protected $dbh; // Connection handle
protected $dbhs = array(); protected $dbhs = array();
protected $table_connections = array();
protected $db_error = false; protected $db_error = false;
protected $db_error_msg = ''; protected $db_error_msg = '';
@ -102,6 +104,8 @@ class rcube_db
$this->db_dsnw_array = self::parse_dsn($db_dsnw); $this->db_dsnw_array = self::parse_dsn($db_dsnw);
$this->db_dsnr_array = self::parse_dsn($db_dsnr); $this->db_dsnr_array = self::parse_dsn($db_dsnr);
$this->db_table_dsn_map = array_map(array($this, 'table_name'), rcube::get_instance()->config->get('db_table_dsn', array()));
} }
/** /**
@ -185,8 +189,9 @@ class rcube_db
* Connect to appropriate database depending on the operation * Connect to appropriate database depending on the operation
* *
* @param string $mode Connection mode (r|w) * @param string $mode Connection mode (r|w)
* @param boolean $force Enforce using the given mode
*/ */
public function db_connect($mode) public function db_connect($mode, $force = false)
{ {
// previous connection failed, don't attempt to connect again // previous connection failed, don't attempt to connect again
if ($this->conn_failure) { if ($this->conn_failure) {
@ -201,7 +206,7 @@ class rcube_db
// Already connected // Already connected
if ($this->db_connected) { if ($this->db_connected) {
// connected to db with the same or "higher" mode (if allowed) // connected to db with the same or "higher" mode (if allowed)
if ($this->db_mode == $mode || $this->db_mode == 'w' && !$this->db_dsnw_noread) { if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->db_dsnw_noread) {
return; return;
} }
} }
@ -217,6 +222,46 @@ class rcube_db
$this->conn_failure = !$this->db_connected; $this->conn_failure = !$this->db_connected;
} }
/**
* Analyze the given SQL statement and select the appropriate connection to use
*/
protected function dsn_select($query)
{
// no replication
if ($this->db_dsnw == $this->db_dsnr) {
return 'w';
}
// Read or write ?
$mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
// find tables involved in this query
if (preg_match_all('/(?:^|\s)(from|update|into|join)\s+'.$this->options['identifier_start'].'?([a-z0-9._]+)'.$this->options['identifier_end'].'?\s+/i', $query, $matches, PREG_SET_ORDER)) {
foreach ($matches as $m) {
$table = $m[2];
// always use direct mapping
if ($this->db_table_dsn_map[$table]) {
$mode = $this->db_table_dsn_map[$table];
break; // primary table rules
}
else if ($mode == 'r') {
// connected to db with the same or "higher" mode for this table
$db_mode = $this->table_connections[$table];
if ($db_mode == 'w' && !$this->db_dsnw_noread) {
$mode = $db_mode;
}
}
}
// remember mode chosen (for primary table)
$table = $matches[0][2];
$this->table_connections[$table];
}
return $mode;
}
/** /**
* Activate/deactivate debug mode * Activate/deactivate debug mode
* *
@ -349,10 +394,7 @@ class rcube_db
{ {
$query = trim($query); $query = trim($query);
// Read or write ? $this->db_connect($this->dsn_select($query), true);
$mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
$this->db_connect($mode);
// check connection before proceeding // check connection before proceeding
if (!$this->is_connected()) { if (!$this->is_connected()) {
@ -877,10 +919,14 @@ class rcube_db
*/ */
public function table_name($table) public function table_name($table)
{ {
static $rcube;
if (!$rcube) {
$rcube = rcube::get_instance(); $rcube = rcube::get_instance();
}
// add prefix to the table name if configured // add prefix to the table name if configured
if ($prefix = $rcube->config->get('db_prefix')) { if (($prefix = $rcube->config->get('db_prefix')) && strpos($table, $prefix) !== 0) {
return $prefix . $table; return $prefix . $table;
} }
@ -898,6 +944,17 @@ class rcube_db
$this->options[$name] = $value; $this->options[$name] = $value;
} }
/**
* Set DSN connection to be used for the given table
*
* @param string Table name
* @param string DSN connection ('r' or 'w') to be used
*/
public function set_table_dsn($table, $mode)
{
$this->db_table_dsn_map[$this->table_name($table)] = $mode;
}
/** /**
* MDB2 DSN string parser * MDB2 DSN string parser
* *

Loading…
Cancel
Save