Update Moment.js from 2.8.3 to 2.8.4
[lhc/web/wiklou.git] / includes / db / Database.php
index eb9d9b3..0b022d1 100644 (file)
@@ -46,6 +46,9 @@ 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;
@@ -1036,6 +1039,20 @@ abstract class DatabaseBase implements IDatabase {
                return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
        }
 
+       /**
+        * Determine whether a SQL statement is sensitive to isolation level.
+        * A SQL statement is considered transactable if its result could vary
+        * depending on the transaction isolation level. Operational commands
+        * such as 'SET' and 'SHOW' are not considered to be transactable.
+        *
+        * @param string $sql
+        * @return bool
+        */
+       public function isTransactableQuery( $sql ) {
+               $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) );
+               return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) );
+       }
+
        /**
         * Run an SQL query and return the result. Normally throws a DBQueryError
         * on failure. If errors are ignored, returns false instead.
@@ -1062,7 +1079,9 @@ abstract class DatabaseBase implements IDatabase {
                global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
 
                $this->mLastQuery = $sql;
-               if ( $this->isWriteQuery( $sql ) ) {
+
+               $isWriteQuery = $this->isWriteQuery( $sql );
+               if ( $isWriteQuery ) {
                        # Set a flag indicating that writes have been done
                        wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
                        $this->mDoneWrites = microtime( true );
@@ -1083,25 +1102,16 @@ abstract class DatabaseBase implements IDatabase {
                // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
                $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
 
-               # If DBO_TRX is set, start a transaction
-               if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
-                       $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK'
-               ) {
-                       # Avoid establishing transactions for SHOW and SET statements too -
-                       # that would delay transaction initializations to once connection
-                       # is really used by application
-                       $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
-                       if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
-                               if ( $wgDebugDBTransactions ) {
-                                       wfDebug( "Implicit transaction start.\n" );
-                               }
-                               $this->begin( __METHOD__ . " ($fname)" );
-                               $this->mTrxAutomatic = true;
+               if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
+                       if ( $wgDebugDBTransactions ) {
+                               wfDebug( "Implicit transaction start.\n" );
                        }
+                       $this->begin( __METHOD__ . " ($fname)" );
+                       $this->mTrxAutomatic = true;
                }
 
                # Keep track of whether the transaction has write queries pending
-               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+               if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
                        $this->mTrxDoneWrites = true;
                        Profiler::instance()->getTransactionProfiler()->transactionWritingIn(
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
@@ -1196,9 +1206,22 @@ 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 ) );
+                       }
                }
 
-               return $this->resultObject( $ret );
+               $res = $this->resultObject( $ret );
+
+               // Destroy profile sections in the opposite order to their creation
+               $queryProfSection = false;
+               $totalProfSection = false;
+
+               return $res;
        }
 
        /**
@@ -2137,16 +2160,36 @@ abstract class DatabaseBase implements IDatabase {
                        } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
                                $list .= "$value";
                        } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
-                               if ( count( $value ) == 0 ) {
+                               // Remove null from array to be handled separately if found
+                               $includeNull = false;
+                               foreach ( array_keys( $value, null, true ) as $nullKey ) {
+                                       $includeNull = true;
+                                       unset( $value[$nullKey] );
+                               }
+                               if ( count( $value ) == 0 && !$includeNull ) {
                                        throw new MWException( __METHOD__ . ": empty input for field $field" );
-                               } elseif ( count( $value ) == 1 ) {
-                                       // Special-case single values, as IN isn't terribly efficient
-                                       // Don't necessarily assume the single key is 0; we don't
-                                       // enforce linear numeric ordering on other arrays here.
-                                       $value = array_values( $value );
-                                       $list .= $field . " = " . $this->addQuotes( $value[0] );
+                               } elseif ( count( $value ) == 0 ) {
+                                       // only check if $field is null
+                                       $list .= "$field IS NULL";
                                } else {
-                                       $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+                                       // IN clause contains at least one valid element
+                                       if ( $includeNull ) {
+                                               // Group subconditions to ensure correct precedence
+                                               $list .= '(';
+                                       }
+                                       if ( count( $value ) == 1 ) {
+                                               // Special-case single values, as IN isn't terribly efficient
+                                               // Don't necessarily assume the single key is 0; we don't
+                                               // enforce linear numeric ordering on other arrays here.
+                                               $value = array_values( $value );
+                                               $list .= $field . " = " . $this->addQuotes( $value[0] );
+                                       } else {
+                                               $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+                                       }
+                                       // if null present in array, append IS NULL
+                                       if ( $includeNull ) {
+                                               $list .= " OR $field IS NULL)";
+                                       }
                                }
                        } elseif ( $value === null ) {
                                if ( $mode == LIST_AND || $mode == LIST_OR ) {
@@ -3639,6 +3682,7 @@ abstract class DatabaseBase implements IDatabase {
         *   calling rollback when no transaction is in progress. This will silently
         *   break any ongoing explicit transaction. Only set the flush flag if you
         *   are sure that it is safe to ignore these warnings in your context.
+        * @throws DBUnexpectedError
         * @since 1.23 Added $flush parameter
         */
        final public function rollback( $fname = __METHOD__, $flush = '' ) {