Remove Profiler::isStub()
[lhc/web/wiklou.git] / includes / db / Database.php
index 29642d0..a805fa5 100644 (file)
  * @ingroup Database
  */
 
-/**
- * Base interface for all DBMS-specific code. At a bare minimum, all of the
- * following must be implemented to support MediaWiki
- *
- * @file
- * @ingroup Database
- */
-interface DatabaseType {
-       /**
-        * Get the type of the DBMS, as it appears in $wgDBtype.
-        *
-        * @return string
-        */
-       function getType();
-
-       /**
-        * Open a connection to the database. Usually aborts on failure
-        *
-        * @param string $server Database server host
-        * @param string $user Database user name
-        * @param string $password Database user password
-        * @param string $dbName Database name
-        * @return bool
-        * @throws DBConnectionError
-        */
-       function open( $server, $user, $password, $dbName );
-
-       /**
-        * Fetch the next row from the given result object, in object form.
-        * Fields can be retrieved with $row->fieldname, with fields acting like
-        * member variables.
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
-        * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchObject( $res );
-
-       /**
-        * Fetch the next row from the given result object, in associative array
-        * form. Fields are retrieved with $row['fieldname'].
-        * If no more rows are available, false is returned.
-        *
-        * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
-        * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
-       function fetchRow( $res );
-
-       /**
-        * Get the number of rows in a result object
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       function numRows( $res );
-
-       /**
-        * Get the number of fields in a result object
-        * @see http://www.php.net/mysql_num_fields
-        *
-        * @param mixed $res A SQL result
-        * @return int
-        */
-       function numFields( $res );
-
-       /**
-        * Get a field name in a result object
-        * @see http://www.php.net/mysql_field_name
-        *
-        * @param mixed $res A SQL result
-        * @param int $n
-        * @return string
-        */
-       function fieldName( $res, $n );
-
-       /**
-        * Get the inserted value of an auto-increment row
-        *
-        * The value inserted should be fetched from nextSequenceValue()
-        *
-        * Example:
-        * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
-        * $dbw->insert( 'page', array( 'page_id' => $id ) );
-        * $id = $dbw->insertId();
-        *
-        * @return int
-        */
-       function insertId();
-
-       /**
-        * Change the position of the cursor in a result object
-        * @see http://www.php.net/mysql_data_seek
-        *
-        * @param mixed $res A SQL result
-        * @param int $row
-        */
-       function dataSeek( $res, $row );
-
-       /**
-        * Get the last error number
-        * @see http://www.php.net/mysql_errno
-        *
-        * @return int
-        */
-       function lastErrno();
-
-       /**
-        * Get a description of the last error
-        * @see http://www.php.net/mysql_error
-        *
-        * @return string
-        */
-       function lastError();
-
-       /**
-        * mysql_fetch_field() wrapper
-        * Returns false if the field doesn't exist
-        *
-        * @param string $table Table name
-        * @param string $field Field name
-        *
-        * @return Field
-        */
-       function fieldInfo( $table, $field );
-
-       /**
-        * Get information about an index into an object
-        * @param string $table Table name
-        * @param string $index Index name
-        * @param string $fname Calling function name
-        * @return mixed Database-specific index description class or false if the index does not exist
-        */
-       function indexInfo( $table, $index, $fname = __METHOD__ );
-
-       /**
-        * Get the number of rows affected by the last write query
-        * @see http://www.php.net/mysql_affected_rows
-        *
-        * @return int
-        */
-       function affectedRows();
-
-       /**
-        * Wrapper for addslashes()
-        *
-        * @param string $s String to be slashed.
-        * @return string Slashed string.
-        */
-       function strencode( $s );
-
-       /**
-        * Returns a wikitext link to the DB's website, e.g.,
-        *   return "[http://www.mysql.com/ MySQL]";
-        * Should at least contain plain text, if for some reason
-        * your database has no website.
-        *
-        * @return string Wikitext of a link to the server software's web site
-        */
-       function getSoftwareLink();
-
-       /**
-        * A string describing the current software version, like from
-        * mysql_get_server_info().
-        *
-        * @return string Version information from the database server.
-        */
-       function getServerVersion();
-
-       /**
-        * A string describing the current software version, and possibly
-        * other details in a user-friendly way. Will be listed on Special:Version, etc.
-        * Use getServerVersion() to get machine-friendly information.
-        *
-        * @return string Version information from the database server
-        */
-       function getServerInfo();
-}
-
 /**
  * Interface for classes that implement or wrap DatabaseBase
  * @ingroup Database
@@ -216,7 +36,7 @@ interface IDatabase {
  * Database abstraction object
  * @ingroup Database
  */
-abstract class DatabaseBase implements IDatabase, DatabaseType {
+abstract class DatabaseBase implements IDatabase {
        /** Number of times to re-try an operation in case of deadlock */
        const DEADLOCK_TRIES = 4;
 
@@ -978,6 +798,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                $this->mPHPError = $errstr;
        }
 
+       /**
+        * Create a log context to pass to wfLogDBError or other logging functions.
+        *
+        * @param array $extras Additional data to add to context
+        * @return array
+        */
+       protected function getLogContext( array $extras = array() ) {
+               return array_merge(
+                       array(
+                               'db_server' => $this->mServer,
+                               'db_name' => $this->mDBname,
+                               'db_user' => $this->mUser,
+                       ),
+                       $extras
+               );
+       }
+
        /**
         * Closes a database connection.
         * if it is open : commits any open transactions
@@ -1117,7 +954,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                # Keep track of whether the transaction has write queries pending
                if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
                        $this->mTrxDoneWrites = true;
-                       Profiler::instance()->transactionWritingIn(
+                       Profiler::instance()->getTransactionProfiler()->transactionWritingIn(
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
                }
 
@@ -1125,7 +962,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                $totalProf = '';
                $isMaster = !is_null( $this->getLBInfo( 'master' ) );
 
-               if ( !Profiler::instance()->isStub() ) {
+               $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 ) {
@@ -1138,9 +976,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        # Include query transaction state
                        $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
 
-                       $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no';
-                       wfProfileIn( $totalProf );
-                       wfProfileIn( $queryProf );
+                       $totalProfSection = $profiler->scopedProfileIn( $totalProf );
+                       $queryProfSection = $profiler->scopedProfileIn( $queryProf );
                }
 
                if ( $this->debug() ) {
@@ -1162,6 +999,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        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
                $ret = $this->doQuery( $commentedSql );
 
@@ -1179,16 +1027,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        $lastError = $this->lastError();
                        $lastErrno = $this->lastErrno();
                        if ( $this->ping() ) {
-                               global $wgRequestTime;
                                wfDebug( "Reconnected\n" );
-                               $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
-                                       : $commentedSql;
-                               $sqlx = strtr( $sqlx, "\t\n", '  ' );
-                               $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
-                               if ( $elapsed < 300 ) {
-                                       # Not a database error to lose a transaction after a minute or two
-                                       wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" );
-                               }
+                               $server = $this->getServer();
+                               $msg = __METHOD__ . ": lost connection to $server; reconnected";
+                               wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+
                                if ( $hadTrx ) {
                                        # Leave $ret as false and let an error be reported.
                                        # Callers may catch the exception and continue to use the DB.
@@ -1206,11 +1049,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
                }
 
-               if ( !Profiler::instance()->isStub() ) {
-                       wfProfileOut( $queryProf );
-                       wfProfileOut( $totalProf );
-               }
-
                return $this->resultObject( $ret );
        }
 
@@ -1235,7 +1073,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        $this->ignoreErrors( $ignore );
                } else {
                        $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
-                       wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
+                       wfLogDBError(
+                               "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
+                               $this->getLogContext( array(
+                                       'method' => __METHOD__,
+                                       'errno' => $errno,
+                                       'error' => $error,
+                                       'sql1line' => $sql1line,
+                                       'fname' => $fname,
+                               ) )
+                       );
                        wfDebug( "SQL ERROR: " . $error . "\n" );
                        throw new DBQueryError( $this, $error, $errno, $sql, $fname );
                }
@@ -2346,7 +2193,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                # Split database and table into proper variables.
                # We reverse the explode so that database.table and table both output
                # the correct table.
-               $dbDetails = explode( '.', $name, 2 );
+               $dbDetails = explode( '.', $name, 3 );
                if ( count( $dbDetails ) == 3 ) {
                        list( $database, $schema, $table ) = $dbDetails;
                        # We don't want any prefix added in this case
@@ -3510,7 +3357,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                                $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
                                        " performing implicit commit!";
                                wfWarn( $msg );
-                               wfLogDBError( $msg );
+                               wfLogDBError( $msg,
+                                       $this->getLogContext( array(
+                                               'method' => __METHOD__,
+                                               'fname' => $fname,
+                                       ) )
+                               );
                        } else {
                                // if the transaction was automatic and has done write operations,
                                // log it if $wgDebugDBTransactions is enabled.
@@ -3524,7 +3376,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                        $this->runOnTransactionPreCommitCallbacks();
                        $this->doCommit( $fname );
                        if ( $this->mTrxDoneWrites ) {
-                               Profiler::instance()->transactionWritingOut(
+                               Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
                                        $this->mServer, $this->mDBname, $this->mTrxShortId );
                        }
                        $this->runOnTransactionIdleCallbacks();
@@ -3604,7 +3456,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                $this->runOnTransactionPreCommitCallbacks();
                $this->doCommit( $fname );
                if ( $this->mTrxDoneWrites ) {
-                       Profiler::instance()->transactionWritingOut(
+                       Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
                }
                $this->runOnTransactionIdleCallbacks();
@@ -3662,7 +3514,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                $this->mTrxPreCommitCallbacks = array(); // cancel
                $this->mTrxAtomicLevels = new SplStack;
                if ( $this->mTrxDoneWrites ) {
-                       Profiler::instance()->transactionWritingOut(
+                       Profiler::instance()->getTransactionProfiler()->transactionWritingOut(
                                $this->mServer, $this->mDBname, $this->mTrxShortId );
                }
        }
@@ -4044,47 +3896,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
         *
         * - '{$var}' should be used for text and is passed through the database's
         *   addQuotes method.
-        * - `{$var}` should be used for identifiers (eg: table and database names),
-        *   it is passed through the database's addIdentifierQuotes method which
+        * - `{$var}` should be used for identifiers (e.g. table and database names).
+        *   It is passed through the database's addIdentifierQuotes method which
         *   can be overridden if the database uses something other than backticks.
-        * - / *$var* / is just encoded, besides traditional table prefix and
-        *   table options its use should be avoided.
+        * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
+        *   database's tableName method.
+        * - / *i* / passes the name that follows through the database's indexName method.
+        * - In all other cases, / *$var* / is left unencoded. Except for table options,
+        *   its use should be avoided. In 1.24 and older, string encoding was applied.
         *
         * @param string $ins SQL statement to replace variables in
         * @return string The new SQL statement with variables replaced
         */
-       protected function replaceSchemaVars( $ins ) {
-               $vars = $this->getSchemaVars();
-               foreach ( $vars as $var => $value ) {
-                       // replace '{$var}'
-                       $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
-                       // replace `{$var}`
-                       $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
-                       // replace /*$var*/
-                       $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
-               }
-
-               return $ins;
-       }
-
-       /**
-        * Replace variables in sourced SQL
-        *
-        * @param string $ins
-        * @return string
-        */
        protected function replaceVars( $ins ) {
-               $ins = $this->replaceSchemaVars( $ins );
-
-               // Table prefixes
-               $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
-                       array( $this, 'tableNameCallback' ), $ins );
-
-               // Index names
-               $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
-                       array( $this, 'indexNameCallback' ), $ins );
-
-               return $ins;
+               $that = $this;
+               $vars = $this->getSchemaVars();
+               return preg_replace_callback(
+                       '!
+                               /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
+                               \'\{\$ (\w+) }\'                  | # 3. addQuotes
+                               `\{\$ (\w+) }`                    | # 4. addIdentifierQuotes
+                               /\*\$ (\w+) \*/                     # 5. leave unencoded
+                       !x',
+                       function ( $m ) use ( $that, $vars ) {
+                               // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
+                               // check for both nonexistent keys *and* the empty string.
+                               if ( isset( $m[1] ) && $m[1] !== '' ) {
+                                       if ( $m[1] === 'i' ) {
+                                               return $that->indexName( $m[2] );
+                                       } else {
+                                               return $that->tableName( $m[2] );
+                                       }
+                               } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
+                                       return $that->addQuotes( $vars[$m[3]] );
+                               } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
+                                       return $that->addIdentifierQuotes( $vars[$m[4]] );
+                               } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
+                                       return $vars[$m[5]];
+                               } else {
+                                       return $m[0];
+                               }
+                       },
+                       $ins
+               );
        }
 
        /**
@@ -4113,26 +3967,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
                return array();
        }
 
-       /**
-        * Table name callback
-        *
-        * @param array $matches
-        * @return string
-        */
-       protected function tableNameCallback( $matches ) {
-               return $this->tableName( $matches[1] );
-       }
-
-       /**
-        * Index name callback
-        *
-        * @param array $matches
-        * @return string
-        */
-       protected function indexNameCallback( $matches ) {
-               return $this->indexName( $matches[1] );
-       }
-
        /**
         * Check to see if a named lock is available. This is non-blocking.
         *