Merge "Make insertSelect() do two separate queries in non-CLI mode"
[lhc/web/wiklou.git] / includes / db / Database.php
index 67afb83..0a660d9 100644 (file)
@@ -76,8 +76,10 @@ abstract class DatabaseBase implements IDatabase {
        protected $mTrxPreCommitCallbacks = [];
        /** @var array[] List of (callable, method name) */
        protected $mTrxEndCallbacks = [];
-       /** @var bool Whether to suppress triggering of post-commit callbacks */
-       protected $suppressPostCommitCallbacks = false;
+       /** @var array[] Map of (name => (callable, method name)) */
+       protected $mTrxRecurringCallbacks = [];
+       /** @var bool Whether to suppress triggering of transaction end callbacks */
+       protected $mTrxEndCallbacksSuppressed = false;
 
        /** @var string */
        protected $mTablePrefix;
@@ -1072,6 +1074,7 @@ abstract class DatabaseBase implements IDatabase {
                try {
                        // Handle callbacks in mTrxEndCallbacks
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+                       $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
                        return null;
                } catch ( Exception $e ) {
                        // Already logged; move on...
@@ -2336,12 +2339,12 @@ abstract class DatabaseBase implements IDatabase {
                        $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
                } catch ( Exception $e ) {
                        if ( $useTrx ) {
-                               $this->rollback( $fname );
+                               $this->rollback( $fname, self::FLUSHING_INTERNAL );
                        }
                        throw $e;
                }
                if ( $useTrx ) {
-                       $this->commit( $fname, self::TRANSACTION_INTERNAL );
+                       $this->commit( $fname, self::FLUSHING_INTERNAL );
                }
 
                return $ok;
@@ -2679,27 +2682,35 @@ abstract class DatabaseBase implements IDatabase {
                        $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
                } else {
                        // If no transaction is active, then make one for this callback
-                       $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
+                       $this->startAtomic( __METHOD__ );
                        try {
                                call_user_func( $callback );
-                               $this->commit( __METHOD__ );
+                               $this->endAtomic( __METHOD__ );
                        } catch ( Exception $e ) {
-                               $this->rollback( __METHOD__ );
+                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
                                throw $e;
                        }
                }
        }
 
+       final public function setTransactionListener( $name, callable $callback = null ) {
+               if ( $callback ) {
+                       $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
+               } else {
+                       unset( $this->mTrxRecurringCallbacks[$name] );
+               }
+       }
+
        /**
-        * Whether to disable running of post-commit callbacks
+        * Whether to disable running of post-COMMIT/ROLLBACK callbacks
         *
         * This method should not be used outside of Database/LoadBalancer
         *
         * @param bool $suppress
         * @since 1.28
         */
-       final public function setPostCommitCallbackSupression( $suppress ) {
-               $this->suppressPostCommitCallbacks = $suppress;
+       final public function setTrxEndCallbackSuppression( $suppress ) {
+               $this->mTrxEndCallbacksSuppressed = $suppress;
        }
 
        /**
@@ -2712,7 +2723,7 @@ abstract class DatabaseBase implements IDatabase {
         * @throws Exception
         */
        public function runOnTransactionIdleCallbacks( $trigger ) {
-               if ( $this->suppressPostCommitCallbacks ) {
+               if ( $this->mTrxEndCallbacksSuppressed ) {
                        return;
                }
 
@@ -2742,7 +2753,7 @@ abstract class DatabaseBase implements IDatabase {
                                        // Some callbacks may use startAtomic/endAtomic, so make sure
                                        // their transactions are ended so other callbacks don't fail
                                        if ( $this->trxLevel() ) {
-                                               $this->rollback( __METHOD__ );
+                                               $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
                                        }
                                }
                        }
@@ -2782,10 +2793,41 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
+       /**
+        * Actually run any "transaction listener" callbacks.
+        *
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @param integer $trigger IDatabase::TRIGGER_* constant
+        * @throws Exception
+        * @since 1.20
+        */
+       public function runTransactionListenerCallbacks( $trigger ) {
+               if ( $this->mTrxEndCallbacksSuppressed ) {
+                       return;
+               }
+
+               /** @var Exception $e */
+               $e = null; // first exception
+
+               foreach ( $this->mTrxRecurringCallbacks as $callback ) {
+                       try {
+                               list( $phpCallback ) = $callback;
+                               $phpCallback( $trigger, $this );
+                       } catch ( Exception $ex ) {
+                               MWExceptionHandler::logException( $ex );
+                               $e = $e ?: $ex;
+                       }
+               }
+
+               if ( $e instanceof Exception ) {
+                       throw $e; // re-throw any first exception
+               }
+       }
+
        final public function startAtomic( $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
                        $this->begin( $fname, self::TRANSACTION_INTERNAL );
-                       $this->mTrxAutomatic = true;
                        // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
                        // in all changes being in one transaction to keep requests transactional.
                        if ( !$this->getFlag( DBO_TRX ) ) {
@@ -2816,7 +2858,7 @@ abstract class DatabaseBase implements IDatabase {
                try {
                        $res = call_user_func_array( $callback, [ $this, $fname ] );
                } catch ( Exception $e ) {
-                       $this->rollback( $fname );
+                       $this->rollback( $fname, self::FLUSHING_INTERNAL );
                        throw $e;
                }
                $this->endAtomic( $fname );
@@ -2838,11 +2880,14 @@ abstract class DatabaseBase implements IDatabase {
                                // @TODO: make this an exception at some point
                                $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
                                wfLogDBError( $msg );
+                               wfWarn( $msg );
                                return; // join the main transaction set
                        }
                } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
                        // @TODO: make this an exception at some point
-                       wfLogDBError( "$fname: Implicit transaction expected (DBO_TRX set)." );
+                       $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
+                       wfLogDBError( $msg );
+                       wfWarn( $msg );
                        return; // let any writes be in the main transaction
                }
 
@@ -2853,7 +2898,7 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxTimestamp = microtime( true );
                $this->mTrxFname = $fname;
                $this->mTrxDoneWrites = false;
-               $this->mTrxAutomatic = false;
+               $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
                $this->mTrxAutomaticAtomic = false;
                $this->mTrxAtomicLevels = [];
                $this->mTrxShortId = wfRandomString( 12 );
@@ -2905,7 +2950,9 @@ abstract class DatabaseBase implements IDatabase {
                                return; // nothing to do
                        } elseif ( $this->mTrxAutomatic ) {
                                // @TODO: make this an exception at some point
-                               wfLogDBError( "$fname: Explicit commit of implicit transaction." );
+                               $msg = "$fname: Explicit commit of implicit transaction.";
+                               wfLogDBError( $msg );
+                               wfWarn( $msg );
                                return; // wait for the main transaction set commit round
                        }
                }
@@ -2923,6 +2970,7 @@ abstract class DatabaseBase implements IDatabase {
                }
 
                $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+               $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
        }
 
        /**
@@ -2968,6 +3016,7 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxIdleCallbacks = []; // clear
                $this->mTrxPreCommitCallbacks = []; // clear
                $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
+               $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
        }
 
        /**
@@ -2985,6 +3034,18 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
+       public function clearSnapshot( $fname = __METHOD__ ) {
+               if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
+                       // This only flushes transactions to clear snapshots, not to write data
+                       throw new DBUnexpectedError(
+                               $this,
+                               "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+                       );
+               }
+
+               $this->commit( $fname, self::FLUSHING_INTERNAL );
+       }
+
        public function explicitTrxActive() {
                return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
        }