summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Bruederli <thomas@roundcube.net>2013-10-07 11:29:52 +0200
committerThomas Bruederli <thomas@roundcube.net>2013-10-07 11:29:52 +0200
commita69f9918cd1d3a0ca5fec28ac71801ff223435ae (patch)
treeeb17de4da51f91f1d3db7869c8b394e8b0d168e4
parent120db629b0645033fd6a477b9f96cc8dad589213 (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.php10
-rw-r--r--program/lib/Roundcube/rcube_db.php73
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