Remove see comment
[lhc/web/wiklou.git] / includes / libs / rdbms / database / Database.php
index 9b4e4ac..f56f380 100644 (file)
@@ -203,16 +203,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
        /** @var array Map of (name => 1) for locks obtained via lock() */
        private $mNamedLocksHeld = [];
+       /** @var array Map of (table name => 1) for TEMPORARY tables */
+       protected $mSessionTempTables = [];
 
        /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
        private $lazyMasterHandle;
 
-       /**
-        * @since 1.21
-        * @var resource File handle for upgrade
-        */
-       protected $fileHandle = null;
-
        /**
         * @since 1.22
         * @var string[] Process cache of VIEWs names in the database
@@ -270,6 +266,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $this->trxProfiler = $params['trxProfiler'];
                $this->connLogger = $params['connLogger'];
                $this->queryLogger = $params['queryLogger'];
+               $this->errorLogger = $params['errorLogger'];
 
                // Set initial dummy domain until open() sets the final DB/prefix
                $this->currentDomain = DatabaseDomain::newUnspecified();
@@ -468,15 +465,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return $old;
        }
 
-       /**
-        * Set the filehandle to copy write statements to.
-        *
-        * @param resource $fh File handle
-        */
-       public function setFileHandle( $fh ) {
-               $this->fileHandle = $fh;
-       }
-
        public function getLBInfo( $name = null ) {
                if ( is_null( $name ) ) {
                        return $this->mLBInfo;
@@ -652,7 +640,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        protected function installErrorHandler() {
                $this->mPHPError = false;
                $this->htmlErrors = ini_set( 'html_errors', '0' );
-               set_error_handler( [ $this, 'connectionerrorLogger' ] );
+               set_error_handler( [ $this, 'connectionErrorLogger' ] );
        }
 
        /**
@@ -674,10 +662,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
+        * This method should not be used outside of Database classes
+        *
         * @param int $errno
         * @param string $errstr
         */
-       public function connectionerrorLogger( $errno, $errstr ) {
+       public function connectionErrorLogger( $errno, $errstr ) {
                $this->mPHPError = $errstr;
        }
 
@@ -734,7 +724,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         */
        abstract protected function closeConnection();
 
-       function reportConnectionError( $error = 'Unknown error' ) {
+       public function reportConnectionError( $error = 'Unknown error' ) {
                $myError = $this->lastError();
                if ( $myError ) {
                        $error = $myError;
@@ -787,11 +777,43 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
        }
 
+       /**
+        * @param string $sql A SQL query
+        * @return bool Whether $sql is SQL for creating/dropping a new TEMPORARY table
+        */
+       protected function registerTempTableOperation( $sql ) {
+               if ( preg_match(
+                       '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       $this->mSessionTempTables[$matches[1]] = 1;
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       unset( $this->mSessionTempTables[$matches[1]] );
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       return isset( $this->mSessionTempTables[$matches[1]] );
+               }
+
+               return false;
+       }
+
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
 
-               $isWrite = $this->isWriteQuery( $sql );
+               $isWrite = $this->isWriteQuery( $sql ) && !$this->registerTempTableOperation( $sql );
                if ( $isWrite ) {
                        $reason = $this->getReadOnlyReason();
                        if ( $reason !== false ) {
@@ -837,7 +859,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        $lastError = $this->lastError();
                        $lastErrno = $this->lastErrno();
                        # Update state tracking to reflect transaction loss due to disconnection
-                       $this->handleTransactionLoss();
+                       $this->handleSessionLoss();
                        if ( $this->reconnect() ) {
                                $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
                                $this->connLogger->warning( $msg );
@@ -866,7 +888,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        $tempIgnore = false; // not recoverable
                                }
                                # Update state tracking to reflect transaction loss
-                               $this->handleTransactionLoss();
+                               $this->handleSessionLoss();
                        }
 
                        $this->reportQueryError(
@@ -979,10 +1001,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return true;
        }
 
-       private function handleTransactionLoss() {
+       private function handleSessionLoss() {
                $this->mTrxLevel = 0;
                $this->mTrxIdleCallbacks = []; // bug 65263
                $this->mTrxPreCommitCallbacks = []; // bug 65263
+               $this->mSessionTempTables = [];
+               $this->mNamedLocksHeld = [];
                try {
                        // Handle callbacks in mTrxEndCallbacks
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
@@ -1079,7 +1103,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array
         * @see DatabaseBase::select()
         */
-       public function makeSelectOptions( $options ) {
+       protected function makeSelectOptions( $options ) {
                $preLimitTail = $postLimitTail = '';
                $startOpts = '';
 
@@ -1168,7 +1192,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @see DatabaseBase::select()
         * @since 1.21
         */
-       public function makeGroupByWithHaving( $options ) {
+       protected function makeGroupByWithHaving( $options ) {
                $sql = '';
                if ( isset( $options['GROUP BY'] ) ) {
                        $gb = is_array( $options['GROUP BY'] )
@@ -1194,7 +1218,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @see DatabaseBase::select()
         * @since 1.21
         */
-       public function makeOrderBy( $options ) {
+       protected function makeOrderBy( $options ) {
                if ( isset( $options['ORDER BY'] ) ) {
                        $ob = is_array( $options['ORDER BY'] )
                                ? implode( ',', $options['ORDER BY'] )
@@ -1206,7 +1230,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return '';
        }
 
-       // See IDatabase::select for the docs for this function
        public function select( $table, $vars, $conds = '', $fname = __METHOD__,
                $options = [], $join_conds = [] ) {
                $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
@@ -1225,19 +1248,24 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
                        ? $options['USE INDEX']
                        : [];
-               $ignoreIndexes = ( isset( $options['IGNORE INDEX'] ) && is_array( $options['IGNORE INDEX'] ) )
+               $ignoreIndexes = (
+                       isset( $options['IGNORE INDEX'] ) &&
+                       is_array( $options['IGNORE INDEX'] )
+               )
                        ? $options['IGNORE INDEX']
                        : [];
 
                if ( is_array( $table ) ) {
                        $from = ' FROM ' .
-                               $this->tableNamesWithIndexClauseOrJOIN( $table, $useIndexes, $ignoreIndexes, $join_conds );
+                               $this->tableNamesWithIndexClauseOrJOIN(
+                                       $table, $useIndexes, $ignoreIndexes, $join_conds );
                } elseif ( $table != '' ) {
                        if ( $table[0] == ' ' ) {
                                $from = ' FROM ' . $table;
                        } else {
                                $from = ' FROM ' .
-                                       $this->tableNamesWithIndexClauseOrJOIN( [ $table ], $useIndexes, $ignoreIndexes, [] );
+                                       $this->tableNamesWithIndexClauseOrJOIN(
+                                               [ $table ], $useIndexes, $ignoreIndexes, [] );
                        }
                } else {
                        $from = '';
@@ -1250,7 +1278,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        if ( is_array( $conds ) ) {
                                $conds = $this->makeList( $conds, self::LIST_AND );
                        }
-                       $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail";
+                       $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
+                               "WHERE $conds $preLimitTail";
                } else {
                        $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
                }
@@ -1367,6 +1396,11 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        public function tableExists( $table, $fname = __METHOD__ ) {
+               $tableRaw = $this->tableName( $table, 'raw' );
+               if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+                       return true; // already known to exist
+               }
+
                $table = $this->tableName( $table );
                $old = $this->ignoreErrors( true );
                $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
@@ -1479,7 +1513,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return implode( ' ', $opts );
        }
 
-       function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+       public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
                $table = $this->tableName( $table );
                $opts = $this->makeUpdateOptions( $options );
                $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
@@ -1590,14 +1624,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                }
        }
 
-       /**
-        * Return aggregated value alias
-        *
-        * @param array $valuedata
-        * @param string $valuename
-        *
-        * @return string
-        */
        public function aggregateValue( $valuedata, $valuename = 'value' ) {
                return $valuename;
        }
@@ -1626,11 +1652,6 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
        }
 
-       /**
-        * @param string $field Field or column to cast
-        * @return string
-        * @since 1.28
-        */
        public function buildStringCast( $field ) {
                return $field;
        }
@@ -1803,7 +1824,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param string|bool $alias Alias (optional)
         * @return string SQL name for aliased table. Will not alias a table to its own name
         */
-       public function tableNameWithAlias( $name, $alias = false ) {
+       protected function tableNameWithAlias( $name, $alias = false ) {
                if ( !$alias || $alias == $name ) {
                        return $this->tableName( $name );
                } else {
@@ -1817,7 +1838,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param array $tables [ [alias] => table ]
         * @return string[] See tableNameWithAlias()
         */
-       public function tableNamesWithAlias( $tables ) {
+       protected function tableNamesWithAlias( $tables ) {
                $retval = [];
                foreach ( $tables as $alias => $table ) {
                        if ( is_numeric( $alias ) ) {
@@ -1837,7 +1858,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param string|bool $alias Alias (optional)
         * @return string SQL name for aliased field. Will not alias a field to its own name
         */
-       public function fieldNameWithAlias( $name, $alias = false ) {
+       protected function fieldNameWithAlias( $name, $alias = false ) {
                if ( !$alias || (string)$alias === (string)$name ) {
                        return $name;
                } else {
@@ -1851,7 +1872,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param array $fields [ [alias] => field ]
         * @return string[] See fieldNameWithAlias()
         */
-       public function fieldNamesWithAlias( $fields ) {
+       protected function fieldNamesWithAlias( $fields ) {
                $retval = [];
                foreach ( $fields as $alias => $field ) {
                        if ( is_numeric( $alias ) ) {
@@ -1899,7 +1920,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        }
                                }
                                if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
-                                       $ignore = $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) );
+                                       $ignore = $this->ignoreIndexClause(
+                                               implode( ',', (array)$ignore_index[$alias] ) );
                                        if ( $ignore != '' ) {
                                                $tableClause .= ' ' . $ignore;
                                        }
@@ -1948,18 +1970,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return string
         */
        protected function indexName( $index ) {
-               // Backwards-compatibility hack
-               $renamed = [
-                       'ar_usertext_timestamp' => 'usertext_timestamp',
-                       'un_user_id' => 'user_id',
-                       'un_user_ip' => 'user_ip',
-               ];
-
-               if ( isset( $renamed[$index] ) ) {
-                       return $renamed[$index];
-               } else {
-                       return $index;
-               }
+               return $index;
        }
 
        public function addQuotes( $s ) {
@@ -1968,6 +1979,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                }
                if ( $s === null ) {
                        return 'NULL';
+               } elseif ( is_bool( $s ) ) {
+                       return (int)$s;
                } else {
                        # This will also quote numeric values. This should be harmless,
                        # and protects against weird problems that occur when they really
@@ -2306,7 +2319,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return $this->insert( $destTable, $rows, $fname, $insertOptions );
        }
 
-       public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
+       protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
                $fname = __METHOD__,
                $insertOptions = [], $selectOptions = []
        ) {
@@ -2331,7 +2344,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        $srcTable = $this->tableName( $srcTable );
                }
 
-               $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+               $sql = "INSERT $insertOptions" .
+                       " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
                        " SELECT $startOpts " . implode( ',', $varMap ) .
                        " FROM $srcTable $useIndex $ignoreIndex ";
 
@@ -2368,7 +2382,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         */
        public function limitResult( $sql, $limit, $offset = false ) {
                if ( !is_numeric( $limit ) ) {
-                       throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+                       throw new DBUnexpectedError( $this,
+                               "Invalid non-numeric limit passed to limitResult()\n" );
                }
 
                return "$sql LIMIT "
@@ -2419,11 +2434,10 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
-        * Determines if the given query error was a connection drop
-        * STUB
+        * Do not use this method outside of Database/DBError classes
         *
         * @param integer|string $errno
-        * @return bool
+        * @return bool Whether the given query error was a connection drop
         */
        public function wasConnectionError( $errno ) {
                return false;
@@ -2788,7 +2802,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        }
                } else {
                        if ( !$this->mTrxLevel ) {
-                               $this->queryLogger->error( "$fname: No transaction to commit, something got out of sync." );
+                               $this->queryLogger->error(
+                                       "$fname: No transaction to commit, something got out of sync." );
                                return; // nothing to do
                        } elseif ( $this->mTrxAutomatic ) {
                                // @TODO: make this an exception at some point
@@ -2914,7 +2929,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
        }
 
-       function listTables( $prefix = null, $fname = __METHOD__ ) {
+       public function listTables( $prefix = null, $fname = __METHOD__ ) {
                throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
        }
 
@@ -2955,7 +2970,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        public function timestamp( $ts = 0 ) {
-               $t = new ConvertableTimestamp( $ts );
+               $t = new ConvertibleTimestamp( $ts );
                // Let errors bubble up to avoid putting garbage in the DB
                return $t->getTimestamp( TS_MW );
        }
@@ -3048,7 +3063,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array|null ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
         * @since 1.27
         */
-       public function getTransactionLagStatus() {
+       protected function getTransactionLagStatus() {
                return $this->mTrxLevel
                        ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
                        : null;
@@ -3060,7 +3075,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
         * @since 1.27
         */
-       public function getApproximateLagStatus() {
+       protected function getApproximateLagStatus() {
                return [
                        'lag'   => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
                        'since' => microtime( true )
@@ -3106,7 +3121,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return 0;
        }
 
-       function maxListLen() {
+       public function maxListLen() {
                return 0;
        }
 
@@ -3141,7 +3156,11 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @throws Exception
         */
        public function sourceFile(
-               $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
+               $filename,
+               $lineCallback = false,
+               $resultCallback = false,
+               $fname = false,
+               $inputCallback = false
        ) {
                MediaWiki\suppressWarnings();
                $fp = fopen( $filename, 'r' );
@@ -3156,7 +3175,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                }
 
                try {
-                       $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
+                       $error = $this->sourceStream(
+                               $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
                } catch ( Exception $e ) {
                        fclose( $fp );
                        throw $e;
@@ -3184,8 +3204,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
         * @param bool|callable $inputCallback Optional function called for each complete query sent
         * @return bool|string
         */
-       public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
-               $fname = __METHOD__, $inputCallback = false
+       public function sourceStream(
+               $fp,
+               $lineCallback = false,
+               $resultCallback = false,
+               $fname = __METHOD__,
+               $inputCallback = false
        ) {
                $cmd = '';
 
@@ -3215,7 +3239,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        if ( $done || feof( $fp ) ) {
                                $cmd = $this->replaceVars( $cmd );
 
-                               if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
+                               if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) {
                                        $res = $this->query( $cmd, $fname );
 
                                        if ( $resultCallback ) {
@@ -3238,14 +3262,15 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        /**
         * Called by sourceStream() to check if we've reached a statement end
         *
-        * @param string $sql SQL assembled so far
-        * @param string $newLine New line about to be added to $sql
+        * @param string &$sql SQL assembled so far
+        * @param string &$newLine New line about to be added to $sql
         * @return bool Whether $newLine contains end of the statement
         */
        public function streamStatementEnd( &$sql, &$newLine ) {
                if ( $this->delimiter ) {
                        $prev = $newLine;
-                       $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
+                       $newLine = preg_replace(
+                               '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
                        if ( $newLine != $prev ) {
                                return true;
                        }
@@ -3441,13 +3466,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        return 'infinity';
                }
 
-               try {
-                       $t = new ConvertableTimestamp( $expiry );
-
-                       return $t->getTimestamp( $format );
-               } catch ( TimestampException $e ) {
-                       return false;
-               }
+               return ConvertibleTimestamp::convert( $format, $expiry );
        }
 
        public function setBigSelects( $value = true ) {
@@ -3487,6 +3506,27 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return (string)$this->mConn;
        }
 
+       /**
+        * Make sure that copies do not share the same client binding handle
+        * @throws DBConnectionError
+        */
+       public function __clone() {
+               $this->connLogger->warning(
+                       "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" .
+                       ( new RuntimeException() )->getTraceAsString()
+               );
+
+               if ( $this->isOpen() ) {
+                       // Open a new connection resource without messing with the old one
+                       $this->mOpened = false;
+                       $this->mConn = false;
+                       $this->mTrxEndCallbacks = []; // don't copy
+                       $this->handleSessionLoss(); // no trx or locks anymore
+                       $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+                       $this->lastPing = microtime( true );
+               }
+       }
+
        /**
         * Called by serialize. Throw an exception when DB connection is serialized.
         * This causes problems on some database engines because the connection is
@@ -3498,7 +3538,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
        }
 
        /**
-        * Run a few simple sanity checks
+        * Run a few simple sanity checks and close dangling connections
         */
        public function __destruct() {
                if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
@@ -3510,5 +3550,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        $fnames = implode( ', ', $danglingWriters );
                        trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
                }
+
+               if ( $this->mConn ) {
+                       // Avoid connection leaks for sanity
+                       $this->closeConnection();
+                       $this->mConn = false;
+                       $this->mOpened = false;
+               }
        }
 }