diff options
author | Thomas Bruederli <thomas@roundcube.net> | 2013-10-07 11:29:52 +0200 |
---|---|---|
committer | Thomas Bruederli <thomas@roundcube.net> | 2013-10-07 11:29:52 +0200 |
commit | a69f9918cd1d3a0ca5fec28ac71801ff223435ae (patch) | |
tree | eb17de4da51f91f1d3db7869c8b394e8b0d168e4 | |
parent | 120db629b0645033fd6a477b9f96cc8dad589213 (diff) |
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
-rw-r--r-- | config/defaults.inc.php | 10 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_db.php | 73 |
2 files changed, 75 insertions, 8 deletions
diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 66db040d3..9afa4ac81 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -41,6 +41,16 @@ $config['db_persistent'] = false; // you can define specific table (and sequence) names 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 diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 2861a9197..b215c3aac 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -31,8 +31,10 @@ class rcube_db protected $db_dsnr; // DSN for read operations protected $db_connected = false; // Already connected ? protected $db_mode; // Connection mode + protected $db_table_dsn_map = array(); protected $dbh; // Connection handle protected $dbhs = array(); + protected $table_connections = array(); protected $db_error = false; protected $db_error_msg = ''; @@ -102,6 +104,8 @@ class rcube_db $this->db_dsnw_array = self::parse_dsn($db_dsnw); $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 * * @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 if ($this->conn_failure) { @@ -201,7 +206,7 @@ class rcube_db // Already connected if ($this->db_connected) { // 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; } } @@ -218,6 +223,46 @@ class rcube_db } /** + * 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 * * @param boolean $dbg True if SQL queries should be logged @@ -349,10 +394,7 @@ class rcube_db { $query = trim($query); - // Read or write ? - $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; - - $this->db_connect($mode); + $this->db_connect($this->dsn_select($query), true); // check connection before proceeding if (!$this->is_connected()) { @@ -877,10 +919,14 @@ class rcube_db */ public function table_name($table) { - $rcube = rcube::get_instance(); + static $rcube; + + if (!$rcube) { + $rcube = rcube::get_instance(); + } // 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; } @@ -899,6 +945,17 @@ class rcube_db } /** + * 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 * * @param string $sequence Secuence name |