summaryrefslogtreecommitdiff
path: root/program/lib/Roundcube/rcube_db.php
diff options
context:
space:
mode:
Diffstat (limited to 'program/lib/Roundcube/rcube_db.php')
-rw-r--r--program/lib/Roundcube/rcube_db.php122
1 files changed, 101 insertions, 21 deletions
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 852070073..aaba28172 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -31,7 +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 = '';
@@ -97,9 +100,12 @@ class rcube_db
$this->db_dsnw = $db_dsnw;
$this->db_dsnr = $db_dsnr;
$this->db_pconn = $pconn;
+ $this->db_dsnw_noread = rcube::get_instance()->config->get('db_dsnw_noread', false);
$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()));
}
/**
@@ -113,6 +119,13 @@ class rcube_db
$this->db_error = false;
$this->db_error_msg = null;
+ // return existing handle
+ if ($this->dbhs[$mode]) {
+ $this->dbh = $this->dbhs[$mode];
+ $this->db_mode = $mode;
+ return $this->dbh;
+ }
+
// Get database specific connection options
$dsn_string = $this->dsn_string($dsn);
$dsn_options = $this->dsn_options($dsn);
@@ -147,6 +160,7 @@ class rcube_db
}
$this->dbh = $dbh;
+ $this->dbhs[$mode] = $dbh;
$this->db_mode = $mode;
$this->db_connected = true;
$this->conn_configure($dsn, $dbh);
@@ -175,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) {
@@ -190,14 +205,13 @@ class rcube_db
// Already connected
if ($this->db_connected) {
- // connected to db with the same or "higher" mode
- if ($this->db_mode == 'w' || $this->db_mode == $mode) {
+ // connected to db with the same or "higher" mode (if allowed)
+ if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->db_dsnw_noread) {
return;
}
}
$dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
-
$this->dsn_connect($dsn, $mode);
// use write-master when read-only fails
@@ -209,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] = $mode;
+ }
+
+ return $mode;
+ }
+
+ /**
* Activate/deactivate debug mode
*
* @param boolean $dbg True if SQL queries should be logged
@@ -340,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()) {
@@ -386,17 +437,7 @@ class rcube_db
$result = $this->dbh->query($query);
if ($result === false) {
- $error = $this->dbh->errorInfo();
-
- if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
- $this->db_error = true;
- $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $this->db_error_msg . " (SQL Query: $query)"
- ), true, false);
- }
+ $result = $this->handle_error($query);
}
$this->last_result = $result;
@@ -405,6 +446,30 @@ class rcube_db
}
/**
+ * Helper method to handle DB errors.
+ * This by default logs the error but could be overriden by a driver implementation
+ *
+ * @param string Query that triggered the error
+ * @return mixed Result to be stored and returned
+ */
+ protected function handle_error($query)
+ {
+ $error = $this->dbh->errorInfo();
+
+ if (empty($this->options['ignore_key_errors']) || !in_array($error[0], array('23000', '23505'))) {
+ $this->db_error = true;
+ $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
+
+ rcube::raise_error(array('code' => 500, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_error_msg . " (SQL Query: $query)"
+ ), true, false);
+ }
+
+ return false;
+ }
+
+ /**
* Get number of affected rows for the last query
*
* @param mixed $result Optional query handle
@@ -854,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;
}
@@ -876,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