Merge "Title: Title::getSubpage should not lose the interwiki prefix"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMysqlBase.php
index e3c2268..1e3fa84 100644 (file)
@@ -24,7 +24,7 @@ namespace Wikimedia\Rdbms;
 
 use DateTime;
 use DateTimeZone;
-use Wikimedia;
+use Wikimedia\AtEase\AtEase;
 use InvalidArgumentException;
 use Exception;
 use RuntimeException;
@@ -96,7 +96,7 @@ abstract class DatabaseMysqlBase extends Database {
         *   - sslCiphers : array list of allowable ciphers [default: null]
         * @param array $params
         */
-       function __construct( array $params ) {
+       public function __construct( array $params ) {
                $this->lagDetectionMethod = $params['lagDetectionMethod'] ?? 'Seconds_Behind_Master';
                $this->lagDetectionOptions = $params['lagDetectionOptions'] ?? [];
                $this->useGTIDs = !empty( $params['useGTIDs' ] );
@@ -125,7 +125,7 @@ abstract class DatabaseMysqlBase extends Database {
                $this->close();
 
                if ( $schema !== null ) {
-                       throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+                       throw new DBExpectedError( $this, __CLASS__ . ": cannot use schemas ('$schema')" );
                }
 
                $this->server = $server;
@@ -194,7 +194,10 @@ abstract class DatabaseMysqlBase extends Database {
 
        protected function doSelectDomain( DatabaseDomain $domain ) {
                if ( $domain->getSchema() !== null ) {
-                       throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+                       throw new DBExpectedError(
+                               $this,
+                               __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
+                       );
                }
 
                $database = $domain->getDatabase();
@@ -241,12 +244,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               Wikimedia\suppressWarnings();
-               $ok = $this->mysqlFreeResult( $res );
-               Wikimedia\restoreWarnings();
+               AtEase::suppressWarnings();
+               $ok = $this->mysqlFreeResult( ResultWrapper::unwrap( $res ) );
+               AtEase::restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
                }
@@ -266,12 +266,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               Wikimedia\suppressWarnings();
-               $row = $this->mysqlFetchObject( $res );
-               Wikimedia\restoreWarnings();
+               AtEase::suppressWarnings();
+               $row = $this->mysqlFetchObject( ResultWrapper::unwrap( $res ) );
+               AtEase::restoreWarnings();
 
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_object does not reset the last errno.
@@ -302,12 +299,9 @@ abstract class DatabaseMysqlBase extends Database {
         * @throws DBUnexpectedError
         */
        public function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-               Wikimedia\suppressWarnings();
-               $row = $this->mysqlFetchArray( $res );
-               Wikimedia\restoreWarnings();
+               AtEase::suppressWarnings();
+               $row = $this->mysqlFetchArray( ResultWrapper::unwrap( $res ) );
+               AtEase::restoreWarnings();
 
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_array does not reset the last errno.
@@ -338,12 +332,13 @@ abstract class DatabaseMysqlBase extends Database {
         * @return int
         */
        function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
+               if ( is_bool( $res ) ) {
+                       $n = 0;
+               } else {
+                       AtEase::suppressWarnings();
+                       $n = $this->mysqlNumRows( ResultWrapper::unwrap( $res ) );
+                       AtEase::restoreWarnings();
                }
-               Wikimedia\suppressWarnings();
-               $n = !is_bool( $res ) ? $this->mysqlNumRows( $res ) : 0;
-               Wikimedia\restoreWarnings();
 
                // Unfortunately, mysql_num_rows does not reset the last errno.
                // We are not checking for any errors here, since
@@ -366,11 +361,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return int
         */
        public function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlNumFields( $res );
+               return $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
        }
 
        /**
@@ -387,11 +378,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string
         */
        public function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldName( $res, $n );
+               return $this->mysqlFieldName( ResultWrapper::unwrap( $res ), $n );
        }
 
        /**
@@ -410,11 +397,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string
         */
        public function fieldType( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlFieldType( $res, $n );
+               return $this->mysqlFieldType( ResultWrapper::unwrap( $res ), $n );
        }
 
        /**
@@ -432,11 +415,7 @@ abstract class DatabaseMysqlBase extends Database {
         * @return bool
         */
        public function dataSeek( $res, $row ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $this->mysqlDataSeek( $res, $row );
+               return $this->mysqlDataSeek( ResultWrapper::unwrap( $res ), $row );
        }
 
        /**
@@ -454,12 +433,12 @@ abstract class DatabaseMysqlBase extends Database {
        public function lastError() {
                if ( $this->conn ) {
                        # Even if it's non-zero, it can still be invalid
-                       Wikimedia\suppressWarnings();
+                       AtEase::suppressWarnings();
                        $error = $this->mysqlError( $this->conn );
                        if ( !$error ) {
                                $error = $this->mysqlError();
                        }
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                } else {
                        $error = $this->mysqlError();
                }
@@ -601,13 +580,14 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function fieldInfo( $table, $field ) {
                $table = $this->tableName( $table );
-               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+               $flags = self::QUERY_SILENCE_ERRORS;
+               $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, $flags );
                if ( !$res ) {
                        return false;
                }
-               $n = $this->mysqlNumFields( $res->result );
+               $n = $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
                for ( $i = 0; $i < $n; $i++ ) {
-                       $meta = $this->mysqlFetchField( $res->result, $i );
+                       $meta = $this->mysqlFetchField( ResultWrapper::unwrap( $res ), $i );
                        if ( $field == $meta->name ) {
                                return new MySQLField( $meta );
                        }
@@ -722,7 +702,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @return bool|int
         */
        protected function getLagFromSlaveStatus() {
-               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__, $flags );
                $row = $res ? $res->fetchObject() : false;
                // If the server is not replicating, there will be no row
                if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
@@ -824,7 +805,8 @@ abstract class DatabaseMysqlBase extends Database {
 
                                // Connect to and query the master; catch errors to avoid outages
                                try {
-                                       $res = $conn->query( 'SELECT @@server_id AS id', $fname );
+                                       $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX;
+                                       $res = $conn->query( 'SELECT @@server_id AS id', $fname, $flags );
                                        $row = $res ? $res->fetchObject() : false;
                                        $id = $row ? (int)$row->id : 0;
                                } catch ( DBError $e ) {
@@ -854,7 +836,8 @@ abstract class DatabaseMysqlBase extends Database {
                        // percision field is not supported in MySQL <= 5.5.
                        $res = $this->query(
                                "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
-                               __METHOD__
+                               __METHOD__,
+                               self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX
                        );
                        $row = $res ? $res->fetchObject() : false;
                } finally {
@@ -1032,7 +1015,9 @@ abstract class DatabaseMysqlBase extends Database {
                        $this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
                        self::SERVER_ID_CACHE_TTL,
                        function () use ( $fname ) {
-                               $res = $this->query( "SELECT @@server_id AS id", $fname );
+                               $flags = self::QUERY_IGNORE_DBO_TRX;
+                               $res = $this->query( "SELECT @@server_id AS id", $fname, $flags );
+
                                return intval( $this->fetchObject( $res )->id );
                        }
                );
@@ -1042,11 +1027,13 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string|null
         */
        protected function getServerUUID() {
+               $fname = __METHOD__;
                return $this->srvCache->getWithSetCallback(
                        $this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServer() ),
                        self::SERVER_ID_CACHE_TTL,
-                       function () {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'" );
+                       function () use ( $fname ) {
+                               $flags = self::QUERY_IGNORE_DBO_TRX;
+                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'", $fname, $flags );
                                $row = $this->fetchObject( $res );
 
                                return $row ? $row->Value : null;
@@ -1060,13 +1047,15 @@ abstract class DatabaseMysqlBase extends Database {
         */
        protected function getServerGTIDs( $fname = __METHOD__ ) {
                $map = [];
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
                // Get global-only variables like gtid_executed
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname );
+               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname, $flags );
                foreach ( $res as $row ) {
                        $map[$row->Variable_name] = $row->Value;
                }
                // Get session-specific (e.g. gtid_domain_id since that is were writes will log)
-               $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname );
+               $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname, $flags );
                foreach ( $res as $row ) {
                        $map[$row->Variable_name] = $row->Value;
                }
@@ -1080,11 +1069,14 @@ abstract class DatabaseMysqlBase extends Database {
         * @return string[] Latest available server status row
         */
        protected function getServerRoleStatus( $role, $fname = __METHOD__ ) {
-               return $this->query( "SHOW $role STATUS", $fname )->fetchRow() ?: [];
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+
+               return $this->query( "SHOW $role STATUS", $fname, $flags )->fetchRow() ?: [];
        }
 
        public function serverIsReadOnly() {
-               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__, $flags );
                $row = $this->fetchObject( $res );
 
                return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
@@ -1149,9 +1141,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function setSessionOptions( array $options ) {
                if ( isset( $options['connTimeout'] ) ) {
+                       $flags = self::QUERY_IGNORE_DBO_TRX;
                        $timeout = (int)$options['connTimeout'];
-                       $this->query( "SET net_read_timeout=$timeout" );
-                       $this->query( "SET net_write_timeout=$timeout" );
+                       $this->query( "SET net_read_timeout=$timeout", __METHOD__, $flags );
+                       $this->query( "SET net_write_timeout=$timeout", __METHOD__, $flags );
                }
        }
 
@@ -1184,8 +1177,10 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                return ( $row->lockstatus == 1 );
        }
@@ -1198,8 +1193,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function lock( $lockName, $method, $timeout = 5 ) {
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                if ( $row->lockstatus == 1 ) {
                        parent::lock( $lockName, $method, $timeout ); // record
@@ -1221,8 +1218,10 @@ abstract class DatabaseMysqlBase extends Database {
         */
        public function unlock( $lockName, $method ) {
                $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
-               $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
-               $row = $this->fetchObject( $result );
+
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method, $flags );
+               $row = $this->fetchObject( $res );
 
                if ( $row->lockstatus == 1 ) {
                        parent::unlock( $lockName, $method ); // record
@@ -1258,13 +1257,13 @@ abstract class DatabaseMysqlBase extends Database {
                }
 
                $sql = "LOCK TABLES " . implode( ',', $items );
-               $this->query( $sql, $method );
+               $this->query( $sql, $method, self::QUERY_IGNORE_DBO_TRX );
 
                return true;
        }
 
        protected function doUnlockTables( $method ) {
-               $this->query( "UNLOCK TABLES", $method );
+               $this->query( "UNLOCK TABLES", $method, self::QUERY_IGNORE_DBO_TRX );
 
                return true;
        }
@@ -1285,7 +1284,7 @@ abstract class DatabaseMysqlBase extends Database {
                                (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
                }
                $encValue = $value ? '1' : '0';
-               $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+               $this->query( "SET sql_big_selects=$encValue", __METHOD__, self::QUERY_IGNORE_DBO_TRX );
        }
 
        /**
@@ -1468,7 +1467,8 @@ abstract class DatabaseMysqlBase extends Database {
         * @return array
         */
        private function getMysqlStatus( $which = "%" ) {
-               $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+               $flags = self::QUERY_IGNORE_DBO_TRX;
+               $res = $this->query( "SHOW STATUS LIKE '{$which}'", __METHOD__, $flags );
                $status = [];
 
                foreach ( $res as $row ) {