Alrighty, now we properly remove old blocks before inserting the new one. (Bug 10080...
[lhc/web/wiklou.git] / includes / Database.php
index b9b4502..5a94942 100644 (file)
@@ -1,5 +1,9 @@
 <?php
 /**
+ * @defgroup Database Database
+ *
+ * @file
+ * @ingroup Database
  * This file deals with MySQL interface functions
  * and query specifics/optimisations
  */
@@ -13,7 +17,7 @@ define( 'DEADLOCK_DELAY_MAX', 1500000 );
 
 /**
  * Database abstraction object
- * @addtogroup Database
+ * @ingroup Database
  */
 class Database {
 
@@ -22,6 +26,7 @@ class Database {
 #------------------------------------------------------------------------------
 
        protected $mLastQuery = '';
+       protected $mPHPError = false;
 
        protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
        protected $mOut, $mOpened = false;
@@ -226,6 +231,14 @@ class Database {
                return $this->$name;
        }
 
+       function getWikiID() {
+               if( $this->mTablePrefix ) {
+                       return "{$this->mDBname}-{$this->mTablePrefix}";
+               } else {
+                       return $this->mDBname;
+               }
+       }
+
 #------------------------------------------------------------------------------
 # Other functions
 #------------------------------------------------------------------------------
@@ -331,21 +344,23 @@ class Database {
                # so we use a short timeout plus a manual retry.
                $this->mConn = false;
                $max = 3;
+               $this->installErrorHandler();
                for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
                        if ( $i > 1 ) {
                                usleep( 1000 );
                        }
                        if ( $this->mFlags & DBO_PERSISTENT ) {
-                               @/**/$this->mConn = mysql_pconnect( $realServer, $user, $password );
+                               $this->mConn = mysql_pconnect( $realServer, $user, $password );
                        } else {
                                # Create a new connection...
-                               @/**/$this->mConn = mysql_connect( $realServer, $user, $password, true );
+                               $this->mConn = mysql_connect( $realServer, $user, $password, true );
                        }
                        if ($this->mConn === false) {
                                #$iplus = $i + 1;
                                #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); 
                        }
                }
+               $phpError = $this->restoreErrorHandler();
                
                wfProfileOut("dbconnect-$server");
 
@@ -384,7 +399,7 @@ class Database {
 
                        // Turn off strict mode if it is on
                } else {
-                       $this->reportConnectionError();
+                       $this->reportConnectionError( $phpError );
                }
 
                $this->mOpened = $success;
@@ -393,6 +408,20 @@ class Database {
        }
        /**@}}*/
 
+       protected function installErrorHandler() {
+               $this->mPHPError = false;
+               set_error_handler( array( $this, 'connectionErrorHandler' ) );
+       }
+
+       protected function restoreErrorHandler() {
+               restore_error_handler();
+               return $this->mPHPError;
+       }
+
+       protected function connectionErrorHandler( $errno,  $errstr ) {
+               $this->mPHPError = $errstr;
+       }
+
        /**
         * Closes a database connection.
         * if it is open : commits any open transactions
@@ -931,10 +960,30 @@ class Database {
         * @param string $fname   Calling function name (use __METHOD__) for logs/profiling
         * @param array  $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
         *                        see Database::makeSelectOptions code for list of supported stuff
+        * @param array $join_conds Associative array of table join conditions (optional)
+        *                        (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
         * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
         */
-       function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
+       function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
        {
+               $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+               return $this->query( $sql, $fname );
+       }
+       
+       /**
+        * SELECT wrapper
+        *
+        * @param mixed  $table   Array or string, table name(s) (prefix auto-added)
+        * @param mixed  $vars    Array or string, field name(s) to be retrieved
+        * @param mixed  $conds   Array or string, condition(s) for WHERE
+        * @param string $fname   Calling function name (use __METHOD__) for logs/profiling
+        * @param array  $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+        *                        see Database::makeSelectOptions code for list of supported stuff
+        * @param array $join_conds Associative array of table join conditions (optional)
+        *                        (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+        * @return string, the SQL text
+        */
+       function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
                if( is_array( $vars ) ) {
                        $vars = implode( ',', $vars );
                }
@@ -942,8 +991,8 @@ class Database {
                        $options = array( $options );
                }
                if( is_array( $table ) ) {
-                       if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
-                               $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
+                       if ( !empty($join_conds) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) )
+                               $from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
                        else
                                $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
                } elseif ($table!='') {
@@ -975,7 +1024,7 @@ class Database {
                if (isset($options['EXPLAIN'])) {
                        $sql = 'EXPLAIN ' . $sql;
                }
-               return $this->query( $sql, $fname );
+               return $sql;
        }
 
        /**
@@ -992,9 +1041,9 @@ class Database {
         *
         * @todo migrate documentation to phpdocumentor format
         */
-       function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array() ) {
+       function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
                $options['LIMIT'] = 1;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
                if ( $res === false )
                        return false;
                if ( !$this->numRows($res) ) {
@@ -1385,7 +1434,9 @@ class Database {
                # Split database and table into proper variables.
                # We reverse the explode so that database.table and table both output
                # the correct table.
-               @list( $table, $database ) = array_reverse( explode( '.', $name, 2 ) );
+               $dbDetails = array_reverse( explode( '.', $name, 2 ) );
+               if( isset( $dbDetails[1] ) ) @list( $table, $database ) = $dbDetails;
+               else                         @list( $table ) = $dbDetails;
                $prefix = $this->mTablePrefix; # Default prefix
                
                # A database name has been specified in input. Quote the table name
@@ -1454,16 +1505,38 @@ class Database {
        /**
         * @private
         */
-       function tableNamesWithUseIndex( $tables, $use_index ) {
+       function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
                $ret = array();
-
-               foreach ( $tables as $table )
-                       if ( @$use_index[$table] !== null )
-                               $ret[] = $this->tableName( $table ) . ' ' . $this->useIndexClause( implode( ',', (array)$use_index[$table] ) );
-                       else
-                               $ret[] = $this->tableName( $table );
-
-               return implode( ',', $ret );
+               $retJOIN = array();
+               $use_index_safe = is_array($use_index) ? $use_index : array();
+               $join_conds_safe = is_array($join_conds) ? $join_conds : array();
+               foreach ( $tables as $table ) {
+                       // Is there a JOIN and INDEX clause for this table?
+                       if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
+                               $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+                               $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+                               $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+                               $retJOIN[] = $tableClause;
+                       // Is there an INDEX clause?
+                       } else if ( isset($use_index_safe[$table]) ) {
+                               $tableClause = $this->tableName( $table );
+                               $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+                               $ret[] = $tableClause;
+                       // Is there a JOIN clause?
+                       } else if ( isset($join_conds_safe[$table]) ) {
+                               $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+                               $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+                               $retJOIN[] = $tableClause;
+                       } else {
+                               $tableClause = $this->tableName( $table );
+                               $ret[] = $tableClause;
+                       }
+               }
+               // We can't separate explicit JOIN clauses with ',', use ' ' for those
+               $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
+               $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
+               // Compile our final table clause
+               return implode(' ',array($straightJoins,$otherJoins) );
        }
 
        /**
@@ -2142,7 +2215,7 @@ class Database {
                }
 
                // Table prefixes
-               $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-z_]*)/',
+               $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-zA-Z_0-9]*)/',
                        array( &$this, 'tableNameCallback' ), $ins );
                return $ins;
        }
@@ -2168,7 +2241,7 @@ class Database {
  * Database abstraction object for mySQL
  * Inherit all methods and properties of Database::Database()
  *
- * @addtogroup Database
+ * @ingroup Database
  * @see Database
  */
 class DatabaseMysql extends Database {
@@ -2181,7 +2254,7 @@ class DatabaseMysql extends Database {
 
 /**
  * Utility class.
- * @addtogroup Database
+ * @ingroup Database
  */
 class DBObject {
        public $mData;
@@ -2201,7 +2274,7 @@ class DBObject {
 
 /**
  * Utility class
- * @addtogroup Database
+ * @ingroup Database
  *
  * This allows us to distinguish a blob from a normal string and an array of strings
  */
@@ -2217,7 +2290,7 @@ class Blob {
 
 /**
  * Utility class.
- * @addtogroup Database
+ * @ingroup Database
  */
 class MySQLField {
        private $name, $tablename, $default, $max_length, $nullable,
@@ -2274,7 +2347,7 @@ class MySQLField {
 
 /**
  * Database error base class
- * @addtogroup Database
+ * @ingroup Database
  */
 class DBError extends MWException {
        public $db;
@@ -2291,7 +2364,7 @@ class DBError extends MWException {
 }
 
 /**
- * @addtogroup Database
+ * @ingroup Database
  */
 class DBConnectionError extends DBError {
        public $error;
@@ -2411,7 +2484,7 @@ border=\"0\" ALT=\"Google\"></A>
 }
 
 /**
- * @addtogroup Database
+ * @ingroup Database
  */
 class DBQueryError extends DBError {
        public $error, $errno, $sql, $fname;
@@ -2467,14 +2540,14 @@ class DBQueryError extends DBError {
 }
 
 /**
- * @addtogroup Database
+ * @ingroup Database
  */
 class DBUnexpectedError extends DBError {}
 
 
 /**
  * Result wrapper for grabbing data queried by someone else
- * @addtogroup Database
+ * @ingroup Database
  */
 class ResultWrapper implements Iterator {
        var $db, $result, $pos = 0, $currentRow = null;