Merge "rdbms: make connection counting logic in LoadBalancer less stateful"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMysqlBase.php
index 36c947f..e3c2268 100644 (file)
@@ -122,9 +122,12 @@ abstract class DatabaseMysqlBase extends Database {
        }
 
        protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
-               # Close/unset connection handle
                $this->close();
 
+               if ( $schema !== null ) {
+                       throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+               }
+
                $this->server = $server;
                $this->user = $user;
                $this->password = $password;
@@ -143,88 +146,52 @@ abstract class DatabaseMysqlBase extends Database {
                        $error = $error ?: $this->lastError();
                        $this->connLogger->error(
                                "Error connecting to {db_server}: {error}",
-                               $this->getLogContext( [
-                                       'method' => __METHOD__,
-                                       'error' => $error,
-                               ] )
+                               $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
                        );
                        $this->connLogger->debug( "DB connection error\n" .
                                "Server: $server, User: $user, Password: " .
                                substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
-
                        throw new DBConnectionError( $this, $error );
                }
 
-               if ( strlen( $dbName ) ) {
-                       $this->selectDomain( new DatabaseDomain( $dbName, null, $tablePrefix ) );
-               } else {
-                       $this->currentDomain = new DatabaseDomain( null, null, $tablePrefix );
-               }
-
-               // Tell the server what we're communicating with
-               if ( !$this->connectInitCharset() ) {
-                       $error = $this->lastError();
-                       $this->queryLogger->error(
-                               "Error setting character set: {error}",
-                               $this->getLogContext( [
-                                       'method' => __METHOD__,
-                                       'error' => $this->lastError(),
-                               ] )
+               try {
+                       $this->currentDomain = new DatabaseDomain(
+                               strlen( $dbName ) ? $dbName : null,
+                               null,
+                               $tablePrefix
                        );
-                       throw new DBConnectionError( $this, "Error setting character set: $error" );
-               }
 
-               // Abstract over any insane MySQL defaults
-               $set = [ 'group_concat_max_len = 262144' ];
-               // Set SQL mode, default is turning them all off, can be overridden or skipped with null
-               if ( is_string( $this->sqlMode ) ) {
-                       $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
-               }
-               // Set any custom settings defined by site config
-               // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
-               foreach ( $this->sessionVars as $var => $val ) {
-                       // Escape strings but not numbers to avoid MySQL complaining
-                       if ( !is_int( $val ) && !is_float( $val ) ) {
-                               $val = $this->addQuotes( $val );
+                       // Abstract over any insane MySQL defaults
+                       $set = [ 'group_concat_max_len = 262144' ];
+                       // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+                       if ( is_string( $this->sqlMode ) ) {
+                               $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
+                       }
+                       // Set any custom settings defined by site config
+                       // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
+                       foreach ( $this->connectionVariables as $var => $val ) {
+                               // Escape strings but not numbers to avoid MySQL complaining
+                               if ( !is_int( $val ) && !is_float( $val ) ) {
+                                       $val = $this->addQuotes( $val );
+                               }
+                               $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
                        }
-                       $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
-               }
 
-               if ( $set ) {
-                       // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
-                       $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
-                       if ( !$success ) {
-                               $error = $this->lastError();
-                               $this->queryLogger->error(
-                                       'Error setting MySQL variables on server {db_server}: {error}',
-                                       $this->getLogContext( [
-                                               'method' => __METHOD__,
-                                               'error' => $error,
-                                       ] )
+                       if ( $set ) {
+                               $this->query(
+                                       'SET ' . implode( ', ', $set ),
+                                       __METHOD__,
+                                       self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY
                                );
-                               throw new DBConnectionError( $this, "Error setting MySQL variables: $error" );
                        }
+               } catch ( Exception $e ) {
+                       // Connection was not fully initialized and is not safe for use
+                       $this->conn = false;
                }
 
-               $this->opened = true;
-
                return true;
        }
 
-       /**
-        * Set the character set information right after connection
-        * @return bool
-        */
-       protected function connectInitCharset() {
-               if ( $this->utf8Mode ) {
-                       // Tell the server we're communicating with it in UTF-8.
-                       // This may engage various charset conversions.
-                       return $this->mysqlSetCharset( 'utf8' );
-               } else {
-                       return $this->mysqlSetCharset( 'binary' );
-               }
-       }
-
        protected function doSelectDomain( DatabaseDomain $domain ) {
                if ( $domain->getSchema() !== null ) {
                        throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
@@ -244,11 +211,12 @@ abstract class DatabaseMysqlBase extends Database {
 
                if ( $database !== $this->getDBname() ) {
                        $sql = 'USE ' . $this->addIdentifierQuotes( $database );
-                       $ret = $this->doQuery( $sql );
-                       if ( $ret === false ) {
-                               $error = $this->lastError();
-                               $errno = $this->lastErrno();
-                               $this->reportQueryError( $error, $errno, $sql, __METHOD__ );
+                       list( $res, $err, $errno ) =
+                               $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
+
+                       if ( $res === false ) {
+                               $this->reportQueryError( $err, $errno, $sql, __METHOD__ );
+                               return false; // unreachable
                        }
                }
 
@@ -269,15 +237,7 @@ abstract class DatabaseMysqlBase extends Database {
        abstract protected function mysqlConnect( $realServer, $dbName );
 
        /**
-        * Set the character set of the MySQL link
-        *
-        * @param string $charset
-        * @return bool
-        */
-       abstract protected function mysqlSetCharset( $charset );
-
-       /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @throws DBUnexpectedError
         */
        public function freeResult( $res ) {
@@ -301,7 +261,7 @@ abstract class DatabaseMysqlBase extends Database {
        abstract protected function mysqlFreeResult( $res );
 
        /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @return stdClass|bool
         * @throws DBUnexpectedError
         */
@@ -368,13 +328,13 @@ abstract class DatabaseMysqlBase extends Database {
         * Fetch a result row as an associative and numeric array
         *
         * @param resource $res Raw result
-        * @return array
+        * @return array|false
         */
        abstract protected function mysqlFetchArray( $res );
 
        /**
         * @throws DBUnexpectedError
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @return int
         */
        function numRows( $res ) {
@@ -402,7 +362,7 @@ abstract class DatabaseMysqlBase extends Database {
        abstract protected function mysqlNumRows( $res );
 
        /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @return int
         */
        public function numFields( $res ) {
@@ -422,7 +382,7 @@ abstract class DatabaseMysqlBase extends Database {
        abstract protected function mysqlNumFields( $res );
 
        /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $n
         * @return string
         */
@@ -437,7 +397,7 @@ abstract class DatabaseMysqlBase extends Database {
        /**
         * Get the name of the specified field in a result
         *
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $n
         * @return string
         */
@@ -445,7 +405,7 @@ abstract class DatabaseMysqlBase extends Database {
 
        /**
         * mysql_field_type() wrapper
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $n
         * @return string
         */
@@ -460,14 +420,14 @@ abstract class DatabaseMysqlBase extends Database {
        /**
         * Get the type of the specified field in a result
         *
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $n
         * @return string
         */
        abstract protected function mysqlFieldType( $res, $n );
 
        /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $row
         * @return bool
         */
@@ -482,7 +442,7 @@ abstract class DatabaseMysqlBase extends Database {
        /**
         * Move internal result pointer
         *
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @param int $row
         * @return bool
         */
@@ -743,7 +703,7 @@ abstract class DatabaseMysqlBase extends Database {
                return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
        }
 
-       public function getLag() {
+       protected function doGetLag() {
                if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
                        return $this->getLagFromPtHeartbeat();
                } else {
@@ -952,21 +912,22 @@ abstract class DatabaseMysqlBase extends Database {
                        $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
                        if ( strpos( $gtidArg, ':' ) !== false ) {
                                // MySQL GTIDs, e.g "source_id:transaction_id"
-                               $res = $this->doQuery( "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)" );
+                               $sql = "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)";
                        } else {
                                // MariaDB GTIDs, e.g."domain:server:sequence"
-                               $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
+                               $sql = "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)";
                        }
                } else {
                        // Wait on the binlog coordinates
                        $encFile = $this->addQuotes( $pos->getLogFile() );
                        $encPos = intval( $pos->getLogPosition()[$pos::CORD_EVENT] );
-                       $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
+                       $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
                }
 
+               list( $res, $err ) = $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
                $row = $res ? $this->fetchRow( $res ) : false;
                if ( !$row ) {
-                       throw new DBExpectedError( $this, "Replication wait failed: {$this->lastError()}" );
+                       throw new DBExpectedError( $this, "Replication wait failed: {$err}" );
                }
 
                // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
@@ -1490,7 +1451,7 @@ abstract class DatabaseMysqlBase extends Database {
        /**
         * @param string $tableName
         * @param string $fName
-        * @return bool|ResultWrapper
+        * @return bool|IResultWrapper
         */
        public function dropTable( $tableName, $fName = __METHOD__ ) {
                if ( !$this->tableExists( $tableName, $fName ) ) {