Merge "Never prefix table names with $wgSharedDB when used in foreign DB"
[lhc/web/wiklou.git] / includes / db / Database.php
index 8b56ab0..8caacf7 100644 (file)
@@ -184,7 +184,7 @@ interface DatabaseType {
         *
         * @return string: wikitext of a link to the server software's web site
         */
-       static function getSoftwareLink();
+       function getSoftwareLink();
 
        /**
         * A string describing the current software version, like from
@@ -204,11 +204,17 @@ interface DatabaseType {
        function getServerInfo();
 }
 
+/**
+ * Interface for classes that implement or wrap DatabaseBase
+ * @ingroup Database
+ */
+interface IDatabase {}
+
 /**
  * Database abstraction object
  * @ingroup Database
  */
-abstract class DatabaseBase implements DatabaseType {
+abstract class DatabaseBase implements IDatabase, DatabaseType {
        /** Number of times to re-try an operation in case of deadlock */
        const DEADLOCK_TRIES = 4;
        /** Minimum time to wait before retry, in microseconds */
@@ -229,9 +235,9 @@ abstract class DatabaseBase implements DatabaseType {
        protected $mConn = null;
        protected $mOpened = false;
 
-       /** @var array of Closure */
+       /** @var callable[] */
        protected $mTrxIdleCallbacks = array();
-       /** @var array of Closure */
+       /** @var callable[] */
        protected $mTrxPreCommitCallbacks = array();
 
        protected $mTablePrefix;
@@ -937,6 +943,7 @@ abstract class DatabaseBase implements DatabaseType {
                # Keep track of whether the transaction has write queries pending
                if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
                        $this->mTrxDoneWrites = true;
+                       Profiler::instance()->transactionWritingIn( $this->mServer, $this->mDBname );
                }
 
                if ( $this->debug() ) {
@@ -1244,7 +1251,7 @@ abstract class DatabaseBase implements DatabaseType {
                        $startOpts .= ' SQL_NO_CACHE';
                }
 
-               if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+               if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
                        $useIndex = $this->useIndexClause( $options['USE INDEX'] );
                } else {
                        $useIndex = '';
@@ -1466,28 +1473,26 @@ abstract class DatabaseBase implements DatabaseType {
                }
 
                $options = (array)$options;
+               $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
+                       ? $options['USE INDEX']
+                       : array();
 
                if ( is_array( $table ) ) {
-                       $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
-                               ? $options['USE INDEX']
-                               : array();
-                       if ( count( $join_conds ) || count( $useIndex ) ) {
-                               $from = ' FROM ' .
-                                       $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
-                       } else {
-                               $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
-                       }
+                       $from = ' FROM ' .
+                               $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
                } elseif ( $table != '' ) {
                        if ( $table[0] == ' ' ) {
                                $from = ' FROM ' . $table;
                        } else {
-                               $from = ' FROM ' . $this->tableName( $table );
+                               $from = ' FROM ' .
+                                       $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() );
                        }
                } else {
                        $from = '';
                }
 
-               list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
+               list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
+                       $this->makeSelectOptions( $options );
 
                if ( !empty( $conds ) ) {
                        if ( is_array( $conds ) ) {
@@ -2266,11 +2271,11 @@ abstract class DatabaseBase implements DatabaseType {
                }
 
                // We can't separate explicit JOIN clauses with ',', use ' ' for those
-               $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
-               $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+               $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+               $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
 
                // Compile our final table clause
-               return implode( ' ', array( $straightJoins, $otherJoins ) );
+               return implode( ' ', array( $implicitJoins, $explicitJoins ) );
        }
 
        /**
@@ -2535,6 +2540,92 @@ abstract class DatabaseBase implements DatabaseType {
                return $this->query( $sql, $fname );
        }
 
+       /**
+        * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
+        *
+        * This updates any conflicting rows (according to the unique indexes) using
+        * the provided SET clause and inserts any remaining (non-conflicted) rows.
+        *
+        * $rows may be either:
+        *   - A single associative array. The array keys are the field names, and
+        *     the values are the values to insert. The values are treated as data
+        *     and will be quoted appropriately. If NULL is inserted, this will be
+        *     converted to a database NULL.
+        *   - An array with numeric keys, holding a list of associative arrays.
+        *     This causes a multi-row INSERT on DBMSs that support it. The keys in
+        *     each subarray must be identical to each other, and in the same order.
+        *
+        * It may be more efficient to leave off unique indexes which are unlikely
+        * to collide. However if you do this, you run the risk of encountering
+        * errors which wouldn't have occurred in MySQL.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+        * returns success.
+        *
+        * @param string $table Table name. This will be passed through DatabaseBase::tableName().
+        * @param array $rows A single row or list of rows to insert
+        * @param array $uniqueIndexes List of single field names or field name tuples
+        * @param array $set An array of values to SET. For each array element,
+        *                the key gives the field name, and the value gives the data
+        *                to set that field to. The data will be quoted by
+        *                DatabaseBase::addQuotes().
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @param array $options of options
+        *
+        * @return bool
+        * @since 1.22
+        */
+       public function upsert(
+               $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+       ) {
+               if ( !count( $rows ) ) {
+                       return true; // nothing to do
+               }
+               $rows = is_array( reset( $rows ) ) ? $rows : array( $rows );
+
+               if ( count( $uniqueIndexes ) ) {
+                       $clauses = array(); // list WHERE clauses that each identify a single row
+                       foreach ( $rows as $row ) {
+                               foreach ( $uniqueIndexes as $index ) {
+                                       $index = is_array( $index ) ? $index : array( $index ); // columns
+                                       $rowKey = array(); // unique key to this row
+                                       foreach ( $index as $column ) {
+                                               $rowKey[$column] = $row[$column];
+                                       }
+                                       $clauses[] = $this->makeList( $rowKey, LIST_AND );
+                               }
+                       }
+                       $where = array( $this->makeList( $clauses, LIST_OR ) );
+               } else {
+                       $where = false;
+               }
+
+               $useTrx = !$this->mTrxLevel;
+               if ( $useTrx ) {
+                       $this->begin( $fname );
+               }
+               try {
+                       # Update any existing conflicting row(s)
+                       if ( $where !== false ) {
+                               $ok = $this->update( $table, $set, $where, $fname );
+                       } else {
+                               $ok = true;
+                       }
+                       # Now insert any non-conflicting row(s)
+                       $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok;
+               } catch ( Exception $e ) {
+                       if ( $useTrx ) {
+                               $this->rollback( $fname );
+                       }
+                       throw $e;
+               }
+               if ( $useTrx ) {
+                       $this->commit( $fname );
+               }
+
+               return $ok;
+       }
+
        /**
         * DELETE where the condition is a join.
         *
@@ -2632,7 +2723,10 @@ abstract class DatabaseBase implements DatabaseType {
                $sql = "DELETE FROM $table";
 
                if ( $conds != '*' ) {
-                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+                       if ( is_array( $conds ) ) {
+                               $conds = $this->makeList( $conds, LIST_AND );
+                       }
+                       $sql .= ' WHERE ' . $conds;
                }
 
                return $this->query( $sql, $fname );
@@ -2972,10 +3066,10 @@ abstract class DatabaseBase implements DatabaseType {
         * after the database is updated so that the jobs will see the data when they actually run.
         * It can also be used for updates that easily cause deadlocks if locks are held too long.
         *
-        * @param Closure $callback
+        * @param callable $callback
         * @since 1.20
         */
-       final public function onTransactionIdle( Closure $callback ) {
+       final public function onTransactionIdle( $callback ) {
                $this->mTrxIdleCallbacks[] = $callback;
                if ( !$this->mTrxLevel ) {
                        $this->runOnTransactionIdleCallbacks();
@@ -2990,10 +3084,10 @@ abstract class DatabaseBase implements DatabaseType {
         * This is useful for updates that easily cause deadlocks if locks are held too long
         * but where atomicity is strongly desired for these updates and some related updates.
         *
-        * @param Closure $callback
+        * @param callable $callback
         * @since 1.22
         */
-       final public function onTransactionPreCommitOrIdle( Closure $callback ) {
+       final public function onTransactionPreCommitOrIdle( $callback ) {
                if ( $this->mTrxLevel ) {
                        $this->mTrxPreCommitCallbacks[] = $callback;
                } else {
@@ -3085,6 +3179,9 @@ abstract class DatabaseBase implements DatabaseType {
 
                        $this->runOnTransactionPreCommitCallbacks();
                        $this->doCommit( $fname );
+                       if ( $this->mTrxDoneWrites ) {
+                               Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+                       }
                        $this->runOnTransactionIdleCallbacks();
                }
 
@@ -3134,6 +3231,9 @@ abstract class DatabaseBase implements DatabaseType {
 
                $this->runOnTransactionPreCommitCallbacks();
                $this->doCommit( $fname );
+               if ( $this->mTrxDoneWrites ) {
+                       Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+               }
                $this->runOnTransactionIdleCallbacks();
        }
 
@@ -3165,6 +3265,9 @@ abstract class DatabaseBase implements DatabaseType {
                $this->doRollback( $fname );
                $this->mTrxIdleCallbacks = array(); // cancel
                $this->mTrxPreCommitCallbacks = array(); // cancel
+               if ( $this->mTrxDoneWrites ) {
+                       Profiler::instance()->transactionWritingOut( $this->mServer, $this->mDBname );
+               }
        }
 
        /**