Revert r34906, r34907, r34928 -- mixing high-level data into low-level storage functi...
[lhc/web/wiklou.git] / includes / Database.php
index 3581abf..6b8e9b0 100644 (file)
@@ -226,6 +226,14 @@ class Database {
                return $this->$name;
        }
 
+       function getWikiID() {
+               if( $this->mTablePrefix ) {
+                       return "{$this->mDBname}-{$this->mTablePrefix}";
+               } else {
+                       return $this->mDBname;
+               }
+       }
+
 #------------------------------------------------------------------------------
 # Other functions
 #------------------------------------------------------------------------------
@@ -295,11 +303,9 @@ class Database {
         * If the failFunction is set to a non-zero integer, returns success
         */
        function open( $server, $user, $password, $dbName ) {
-               global $wguname;
+               global $wguname, $wgAllDBsAreLocalhost;
                wfProfileIn( __METHOD__ );
 
-               $server = 'localhost'; debugging_code_left_in();
-
                # Test for missing mysql.so
                # First try to load it
                if (!@extension_loaded('mysql')) {
@@ -312,6 +318,12 @@ class Database {
                        throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
                }
 
+               # Debugging hack -- fake cluster
+               if ( $wgAllDBsAreLocalhost ) {
+                       $realServer = 'localhost';
+               } else {
+                       $realServer = $server;
+               }
                $this->close();
                $this->mServer = $server;
                $this->mUser = $user;
@@ -332,10 +344,10 @@ class Database {
                                usleep( 1000 );
                        }
                        if ( $this->mFlags & DBO_PERSISTENT ) {
-                               @/**/$this->mConn = mysql_pconnect( $server, $user, $password );
+                               @/**/$this->mConn = mysql_pconnect( $realServer, $user, $password );
                        } else {
                                # Create a new connection...
-                               @/**/$this->mConn = mysql_connect( $server, $user, $password, true );
+                               @/**/$this->mConn = mysql_connect( $realServer, $user, $password, true );
                        }
                        if ($this->mConn === false) {
                                #$iplus = $i + 1;
@@ -443,10 +455,10 @@ class Database {
         * @throws DBQueryError Thrown when the database returns an error of any kind
         */
        public function query( $sql, $fname = '', $tempIgnore = false ) {
-               global $wgProfiling;
+               global $wgProfiler;
 
                $isMaster = !is_null( $this->getLBInfo( 'master' ) );
-               if ( $wgProfiling ) {
+               if ( isset( $wgProfiler ) ) {
                        # generalizeSQL will probably cut down the query to reasonable
                        # logging size most of the time. The substr is really just a sanity check.
 
@@ -529,7 +541,7 @@ class Database {
                        $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
                }
 
-               if ( $wgProfiling ) {
+               if ( isset( $wgProfiler ) ) {
                        wfProfileOut( $queryProf );
                        wfProfileOut( $totalProf );
                }
@@ -927,10 +939,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 );
                }
@@ -938,8 +970,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) || 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!='') {
@@ -971,7 +1003,7 @@ class Database {
                if (isset($options['EXPLAIN'])) {
                        $sql = 'EXPLAIN ' . $sql;
                }
-               return $this->query( $sql, $fname );
+               return $sql;
        }
 
        /**
@@ -1351,30 +1383,64 @@ class Database {
        /**
         * Format a table name ready for use in constructing an SQL query
         *
-        * This does two important things: it quotes table names which as necessary,
-        * and it adds a table prefix if there is one.
+        * This does two important things: it quotes the table names to clean them up,
+        * and it adds a table prefix if only given a table name with no quotes.
         *
         * All functions of this object which require a table name call this function
         * themselves. Pass the canonical name to such functions. This is only needed
         * when calling query() directly.
         *
         * @param string $name database table name
+        * @return string full database name
         */
        function tableName( $name ) {
-               global $wgSharedDB;
-               # Skip quoted literals
-               if ( $name{0} != '`' ) {
-                       if ( $this->mTablePrefix !== '' &&  strpos( $name, '.' ) === false ) {
-                               $name = "{$this->mTablePrefix}$name";
-                       }
-                       if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) {
-                               $name = "`$wgSharedDB`.`$name`";
-                       } else {
-                               # Standard quoting
-                               $name = "`$name`";
-                       }
+               global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+               # Skip the entire process when we have a string quoted on both ends.
+               # Note that we check the end so that we will still quote any use of
+               # use of `database`.table. But won't break things if someone wants
+               # to query a database table with a dot in the name.
+               if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
+               
+               # Lets test for any bits of text that should never show up in a table
+               # name. Basically anything like JOIN or ON which are actually part of
+               # SQL queries, but may end up inside of the table value to combine
+               # sql. Such as how the API is doing.
+               # Note that we use a whitespace test rather than a \b test to avoid
+               # any remote case where a word like on may be inside of a table name
+               # surrounded by symbols which may be considered word breaks.
+               if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
+               
+               # 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 ) );
+               $prefix = $this->mTablePrefix; # Default prefix
+               
+               # A database name has been specified in input. Quote the table name
+               # because we don't want any prefixes added.
+               if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
+               
+               # Note that we use the long format because php will complain in in_array if
+               # the input is not an array, and will complain in is_array if it is not set.
+               if( !isset( $database ) # Don't use shared database if pre selected.
+                && isset( $wgSharedDB ) # We have a shared database
+                && $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
+                && isset( $wgSharedTables )
+                && is_array( $wgSharedTables )
+                && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
+                       $database = $wgSharedDB;
+                       $prefix   = isset( $wgSharedprefix ) ? $wgSharedprefix : $prefix;
                }
-               return $name;
+               
+               # Quote the $database and $table and apply the prefix if not quoted.
+               if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+               $table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
+               
+               # Merge our database and table into our final table name.
+               $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
+               
+               # We're finished, return.
+               return $tableName;
        }
 
        /**
@@ -1416,16 +1482,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) );
        }
 
        /**
@@ -1630,7 +1718,7 @@ class Database {
                if( !is_numeric($limit) ) {
                        throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
                }
-               return " $sql LIMIT "
+               return "$sql LIMIT "
                                . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
                                . "{$limit} ";
        }
@@ -1651,6 +1739,18 @@ class Database {
                return " IF($cond, $trueVal, $falseVal) ";
        }
 
+       /**
+        * Returns a comand for str_replace function in SQL query.
+        * Uses REPLACE() in MySQL
+        *
+        * @param string $orig String or column to modify
+        * @param string $old String or column to seek
+        * @param string $new String or column to replace with
+        */
+       function strreplace( $orig, $old, $new ) {
+               return "REPLACE({$orig}, {$old}, {$new})";
+       }
+
        /**
         * Determines if the last failure was due to a deadlock
         */
@@ -2171,7 +2271,7 @@ class Blob {
  */
 class MySQLField {
        private $name, $tablename, $default, $max_length, $nullable,
-               $is_pk, $is_unique, $is_key, $type;
+               $is_pk, $is_unique, $is_multiple, $is_key, $type;
        function __construct ($info) {
                $this->name = $info->name;
                $this->tablename = $info->table;