Merge "Add autocomplete for WhatLinksHere subpages"
[lhc/web/wiklou.git] / includes / db / Database.php
index 33c5cfb..054f27a 100644 (file)
@@ -1039,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.
@@ -1088,21 +1102,12 @@ 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
@@ -2155,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 ) {
@@ -3657,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 = '' ) {
@@ -3936,7 +3962,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;
                }