DatabaseBase: Document behavior of nulls in array conditions
[lhc/web/wiklou.git] / includes / db / Database.php
index 0b022d1..0a2e221 100644 (file)
@@ -46,9 +46,6 @@ abstract class DatabaseBase implements IDatabase {
        /** Maximum time to wait before retry */
        const DEADLOCK_DELAY_MAX = 1500000;
 
-       /** How many row changes in a write query trigger a log entry */
-       const LOG_WRITE_THRESHOLD = 300;
-
        protected $mLastQuery = '';
        protected $mDoneWrites = false;
        protected $mPHPError = false;
@@ -817,6 +814,10 @@ abstract class DatabaseBase implements IDatabase {
                if ( $user ) {
                        $this->open( $server, $user, $password, $dbName );
                }
+
+               $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+               $trxProf = Profiler::instance()->getTransactionProfiler();
+               $trxProf->recordConnection( $this->mServer, $this->mDBname, $isMaster );
        }
 
        /**
@@ -1082,8 +1083,11 @@ abstract class DatabaseBase implements IDatabase {
 
                $isWriteQuery = $this->isWriteQuery( $sql );
                if ( $isWriteQuery ) {
+                       if ( !$this->mDoneWrites ) {
+                               wfDebug( __METHOD__ . ': Writes done: ' .
+                                       DatabaseBase::generalizeSQL( $sql ) . "\n" );
+                       }
                        # Set a flag indicating that writes have been done
-                       wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
                        $this->mDoneWrites = microtime( true );
                }
 
@@ -1117,24 +1121,21 @@ abstract class DatabaseBase implements IDatabase {
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
                }
 
-               $queryProf = '';
-               $totalProf = '';
                $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+               # generalizeSQL will probably cut down the query to reasonable
+               # logging size most of the time. The substr is really just a sanity check.
+               if ( $isMaster ) {
+                       $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+                       $totalProf = 'DatabaseBase::query-master';
+               } else {
+                       $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+                       $totalProf = 'DatabaseBase::query';
+               }
+               # Include query transaction state
+               $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
 
                $profiler = Profiler::instance();
                if ( !$profiler instanceof ProfilerStub ) {
-                       # generalizeSQL will probably cut down the query to reasonable
-                       # logging size most of the time. The substr is really just a sanity check.
-                       if ( $isMaster ) {
-                               $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-                               $totalProf = 'DatabaseBase::query-master';
-                       } else {
-                               $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-                               $totalProf = 'DatabaseBase::query';
-                       }
-                       # Include query transaction state
-                       $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
-
                        $totalProfSection = $profiler->scopedProfileIn( $totalProf );
                        $queryProfSection = $profiler->scopedProfileIn( $queryProf );
                }
@@ -1158,19 +1159,12 @@ abstract class DatabaseBase implements IDatabase {
                        throw new DBUnexpectedError( $this, "DB connection was already closed." );
                }
 
-               # Log the query time and feed it into the DB trx profiler
-               if ( $queryProf != '' ) {
-                       $queryStartTime = microtime( true );
-                       $queryProfile = new ScopedCallback(
-                               function () use ( $queryStartTime, $queryProf, $isMaster ) {
-                                       $trxProfiler = Profiler::instance()->getTransactionProfiler();
-                                       $trxProfiler->recordQueryCompletion( $queryProf, $queryStartTime, $isMaster );
-                               }
-                       );
-               }
-
                # Do the query and handle errors
+               $startTime = microtime( true );
                $ret = $this->doQuery( $commentedSql );
+               # Log the query time and feed it into the DB trx profiler
+               $profiler->getTransactionProfiler()->recordQueryCompletion(
+                       $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
 
                MWDebug::queryTime( $queryId );
 
@@ -1197,7 +1191,11 @@ abstract class DatabaseBase implements IDatabase {
                                        $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
                                } else {
                                        # Should be safe to silently retry (no trx and thus no callbacks)
+                                       $startTime = microtime( true );
                                        $ret = $this->doQuery( $commentedSql );
+                                       # Log the query time and feed it into the DB trx profiler
+                                       $profiler->getTransactionProfiler()->recordQueryCompletion(
+                                               $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
                                }
                        } else {
                                wfDebug( "Failed\n" );
@@ -1205,14 +1203,8 @@ abstract class DatabaseBase implements IDatabase {
                }
 
                if ( false === $ret ) {
-                       $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
-               } else {
-                       $n = $this->affectedRows();
-                       if ( $isWriteQuery && $n > self::LOG_WRITE_THRESHOLD && PHP_SAPI !== 'cli' ) {
-                               wfDebugLog( 'DBPerformance',
-                                       "Query affected $n rows:\n" .
-                                       DatabaseBase::generalizeSQL( $sql ) . "\n" . wfBacktrace( true ) );
-                       }
+                       $this->reportQueryError(
+                               $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
                }
 
                $res = $this->resultObject( $ret );
@@ -1391,9 +1383,13 @@ abstract class DatabaseBase implements IDatabase {
         *
         * @return bool|mixed The value from the field, or false on failure.
         */
-       public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
-               $options = array()
+       public function selectField(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = array()
        ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+               }
+
                if ( !is_array( $options ) ) {
                        $options = array( $options );
                }
@@ -1401,7 +1397,6 @@ abstract class DatabaseBase implements IDatabase {
                $options['LIMIT'] = 1;
 
                $res = $this->select( $table, $var, $cond, $fname, $options );
-
                if ( $res === false || !$this->numRows( $res ) ) {
                        return false;
                }
@@ -1415,6 +1410,48 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
+       /**
+        * A SELECT wrapper which returns a list of single field values from result rows.
+        *
+        * Usually throws a DBQueryError on failure. If errors are explicitly
+        * ignored, returns false on failure.
+        *
+        * If no result rows are returned from the query, false is returned.
+        *
+        * @param string|array $table Table name. See DatabaseBase::select() for details.
+        * @param string $var The field name to select. This must be a valid SQL
+        *   fragment: do not use unvalidated user input.
+        * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+        * @param string $fname The function name of the caller.
+        * @param string|array $options The query options. See DatabaseBase::select() for details.
+        *
+        * @return bool|array The values from the field, or false on failure
+        * @since 1.25
+        */
+       public function selectFieldValues(
+               $table, $var, $cond = '', $fname = __METHOD__, $options = array()
+       ) {
+               if ( $var === '*' ) { // sanity
+                       throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = array( $options );
+               }
+
+               $res = $this->select( $table, $var, $cond, $fname, $options );
+               if ( $res === false ) {
+                       return false;
+               }
+
+               $values = array();
+               foreach ( $res as $row ) {
+                       $values[] = $row->$var;
+               }
+
+               return $values;
+       }
+
        /**
         * Returns an optional USE INDEX clause to go after the table, and a
         * string to go at the end of the query.
@@ -1601,9 +1638,10 @@ abstract class DatabaseBase implements IDatabase {
         *     - If the value of such an array element is a scalar (such as a
         *       string), it will be treated as data and thus quoted appropriately.
         *       If it is null, an IS NULL clause will be added.
-        *     - If the value is an array, an IN(...) clause will be constructed,
-        *       such that the field name may match any of the elements in the
-        *       array. The elements of the array will be quoted.
+        *     - If the value is an array, an IN (...) clause will be constructed
+        *       from its non-null elements, and an IS NULL clause will be added
+        *       if null is present, such that the field may match any of the
+        *       elements in the array. The non-null elements will be quoted.
         *
         * Note that expressions are often DBMS-dependent in their syntax.
         * DBMS-independent wrappers are provided for constructing several types of
@@ -1822,7 +1860,7 @@ abstract class DatabaseBase implements IDatabase {
 
                if ( $res ) {
                        $row = $this->fetchRow( $res );
-                       $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
                }
 
                return $rows;
@@ -1852,7 +1890,7 @@ abstract class DatabaseBase implements IDatabase {
 
                if ( $res ) {
                        $row = $this->fetchRow( $res );
-                       $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+                       $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
                }
 
                return $rows;
@@ -3962,7 +4000,7 @@ abstract class DatabaseBase implements IDatabase {
 
                try {
                        $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
-               } catch ( MWException $e ) {
+               } catch ( Exception $e ) {
                        fclose( $fp );
                        throw $e;
                }