Merge "database: Simplify selectSQLText() and fix USE INDEX bug"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 26 Jun 2013 04:27:35 +0000 (04:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 26 Jun 2013 04:27:35 +0000 (04:27 +0000)
1  2 
includes/db/Database.php

diff --combined includes/db/Database.php
@@@ -229,9 -229,9 +229,9 @@@ abstract class DatabaseBase implements 
        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;
                        $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 = '';
                }
  
                $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 ) ) {
                }
  
                // 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 ) );
        }
  
        /**
                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.
         *
                $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 );
         * 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();
         * 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 {