rdbms: make getMasterPos() ignore GTIDs outside of gtid_domain_id
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / rdbms / database / DatabaseMysqlBaseTest.php
index 5fcca1a..a4edbe7 100644 (file)
  * @copyright © 2013 Wikimedia Foundation and contributors
  */
 
-use Wikimedia\Rdbms\TransactionProfiler;
-use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\MySQLMasterPos;
-use Wikimedia\Rdbms\DatabaseMysqlBase;
-use Wikimedia\Rdbms\DatabaseMysqli;
-use Wikimedia\Rdbms\Database;
-
-/**
- * Fake class around abstract class so we can call concrete methods.
- */
-class FakeDatabaseMysqlBase extends DatabaseMysqlBase {
-       // From Database
-       function __construct() {
-               $this->profiler = new ProfilerStub( [] );
-               $this->trxProfiler = new TransactionProfiler();
-               $this->cliMode = true;
-               $this->connLogger = new \Psr\Log\NullLogger();
-               $this->queryLogger = new \Psr\Log\NullLogger();
-               $this->errorLogger = function ( Exception $e ) {
-                       wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
-               };
-               $this->currentDomain = DatabaseDomain::newUnspecified();
-       }
-
-       protected function closeConnection() {
-       }
-
-       protected function doQuery( $sql ) {
-       }
-
-       protected function fetchAffectedRowCount() {
-       }
-
-       // From DatabaseMysqli
-       protected function mysqlConnect( $realServer ) {
-       }
-
-       protected function mysqlSetCharset( $charset ) {
-       }
-
-       protected function mysqlFreeResult( $res ) {
-       }
-
-       protected function mysqlFetchObject( $res ) {
-       }
-
-       protected function mysqlFetchArray( $res ) {
-       }
-
-       protected function mysqlNumRows( $res ) {
-       }
-
-       protected function mysqlNumFields( $res ) {
-       }
-
-       protected function mysqlFieldName( $res, $n ) {
-       }
-
-       protected function mysqlFieldType( $res, $n ) {
-       }
-
-       protected function mysqlDataSeek( $res, $row ) {
-       }
-
-       protected function mysqlError( $conn = null ) {
-       }
-
-       protected function mysqlFetchField( $res, $n ) {
-       }
-
-       protected function mysqlRealEscapeString( $s ) {
-       }
-
-       function insertId() {
-       }
-
-       function lastErrno() {
-       }
-
-       function affectedRows() {
-       }
-
-       function getServerVersion() {
-       }
-}
+use Wikimedia\TestingAccessWrapper;
 
 class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
 
@@ -118,7 +35,11 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
         * @covers Wikimedia\Rdbms\DatabaseMysqlBase::addIdentifierQuotes
         */
        public function testAddIdentifierQuotes( $expected, $in ) {
-               $db = new FakeDatabaseMysqlBase();
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( null )
+                       ->getMock();
+
                $quoted = $db->addIdentifierQuotes( $in );
                $this->assertEquals( $expected, $quoted );
        }
@@ -216,12 +137,15 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
                        $db->listViews( '' ) );
        }
 
+       /**
+        * @covers Wikimedia\Rdbms\MySQLMasterPos
+        */
        public function testBinLogName() {
                $pos = new MySQLMasterPos( "db1052.2424/4643", 1 );
 
-               $this->assertEquals( "db1052", $pos->binlog );
+               $this->assertEquals( "db1052", $pos->getLogName() );
                $this->assertEquals( "db1052.2424", $pos->getLogFile() );
-               $this->assertEquals( [ 2424, 4643 ], $pos->pos );
+               $this->assertEquals( [ 2424, 4643 ], $pos->getLogPosition() );
        }
 
        /**
@@ -276,20 +200,20 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
                        ],
                        // MySQL GTID style
                        [
-                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:23', $now ),
-                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:24', $now ),
+                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-23', $now ),
+                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-24', $now ),
                                true,
                                false
                        ],
                        [
-                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', $now ),
-                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:100', $now ),
+                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:5-99', $now ),
+                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ),
                                true,
                                false
                        ],
                        [
-                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', $now ),
-                               new MySQLMasterPos( '1E11FA47-71CA-11E1-9E33-C80AA9429562:100', $now ),
+                               new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:1-99', $now ),
+                               new MySQLMasterPos( '1E11FA47-71CA-11E1-9E33-C80AA9429562:1-100', $now ),
                                false,
                                false
                        ],
@@ -407,17 +331,17 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
                        ],
                        [
                                new MySQLMasterPos(
-                                       '2E11FA47-71CA-11E1-9E33-C80AA9429562:5,' .
-                                       '3E11FA47-71CA-11E1-9E33-C80AA9429562:99,' .
-                                       '7E11FA47-71CA-11E1-9E33-C80AA9429562:30',
+                                       '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,' .
+                                       '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99,' .
+                                       '7E11FA47-71CA-11E1-9E33-C80AA9429562:1-30',
                                        1
                                ),
                                new MySQLMasterPos(
-                                       '1E11FA47-71CA-11E1-9E33-C80AA9429562:100,' .
-                                       '3E11FA47-71CA-11E1-9E33-C80AA9429562:66',
+                                       '1E11FA47-71CA-11E1-9E33-C80AA9429562:30-100,' .
+                                       '3E11FA47-71CA-11E1-9E33-C80AA9429562:30-66',
                                        1
                                ),
-                               [ '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ]
+                               [ '3E11FA47-71CA-11E1-9E33-C80AA9429562:20-99' ]
                        ]
                ];
        }
@@ -477,23 +401,152 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
        }
 
        /**
-        * @expectedException UnexpectedValueException
-        * @covers Wikimedia\Rdbms\Database::setFlag
+        * @dataProvider provideGtidData
+        * @covers Wikimedia\Rdbms\MySQLMasterPos
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getReplicaPos
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getMasterPos
         */
-       public function testDBOIgnoreSet() {
-               $db = new FakeDatabaseMysqlBase();
+       public function testServerGtidTable( $gtable, $rBLtable, $mBLtable, $rGTIDs, $mGTIDs ) {
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [
+                               'useGTIDs',
+                               'getServerGTIDs',
+                               'getServerRoleStatus',
+                               'getServerId',
+                               'getServerUUID'
+                       ] )
+                       ->getMock();
 
-               $db->setFlag( Database::DBO_IGNORE );
+               $db->method( 'useGTIDs' )->willReturn( true );
+               $db->method( 'getServerGTIDs' )->willReturn( $gtable );
+               $db->method( 'getServerRoleStatus' )->willReturnCallback(
+                       function ( $role ) use ( $rBLtable, $mBLtable ) {
+                               if ( $role === 'SLAVE' ) {
+                                       return $rBLtable;
+                               } elseif ( $role === 'MASTER' ) {
+                                       return $mBLtable;
+                               }
+
+                               return null;
+                       }
+               );
+               $db->method( 'getServerId' )->willReturn( 1 );
+               $db->method( 'getServerUUID' )->willReturn( '2E11FA47-71CA-11E1-9E33-C80AA9429562' );
+
+               if ( is_array( $rGTIDs ) ) {
+                       $this->assertEquals( $rGTIDs, $db->getReplicaPos()->getGTIDs() );
+               } else {
+                       $this->assertEquals( false, $db->getReplicaPos() );
+               }
+               if ( is_array( $mGTIDs ) ) {
+                       $this->assertEquals( $mGTIDs, $db->getMasterPos()->getGTIDs() );
+               } else {
+                       $this->assertEquals( false, $db->getMasterPos() );
+               }
        }
 
-       /**
-        * @expectedException UnexpectedValueException
-        * @covers Wikimedia\Rdbms\Database::clearFlag
-        */
-       public function testDBOIgnoreClear() {
-               $db = new FakeDatabaseMysqlBase();
-
-               $db->clearFlag( Database::DBO_IGNORE );
+       public static function provideGtidData() {
+               return [
+                       // MariaDB
+                       [
+                               [
+                                       'gtid_domain_id' => 100,
+                                       'gtid_current_pos' => '100-13-77',
+                                       'gtid_binlog_pos' => '100-13-77',
+                                       'gtid_slave_pos' => null // master
+                               ],
+                               [],
+                               [
+                                       'File' => 'host.1600',
+                                       'Pos' => '77'
+                               ],
+                               [ '100' => '100-13-77' ],
+                               [ '100' => '100-13-77' ]
+                       ],
+                       [
+                               [
+                                       'gtid_domain_id' => 100,
+                                       'gtid_current_pos' => '100-13-77',
+                                       'gtid_binlog_pos' => '100-13-77',
+                                       'gtid_slave_pos' => '100-13-77' // replica
+                               ],
+                               [
+                                       'Relay_Master_Log_File' => 'host.1600',
+                                       'Exec_Master_Log_Pos' => '77'
+                               ],
+                               [],
+                               [ '100' => '100-13-77' ],
+                               [ '100' => '100-13-77' ]
+                       ],
+                       [
+                               [
+                                       'gtid_current_pos' => '100-13-77',
+                                       'gtid_binlog_pos' => '100-13-77',
+                                       'gtid_slave_pos' => '100-13-77' // replica
+                               ],
+                               [
+                                       'Relay_Master_Log_File' => 'host.1600',
+                                       'Exec_Master_Log_Pos' => '77'
+                               ],
+                               [],
+                               [ '100' => '100-13-77' ],
+                               [ '100' => '100-13-77' ]
+                       ],
+                       // MySQL
+                       [
+                               [
+                                       'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77'
+                               ],
+                               [
+                                       'Relay_Master_Log_File' => 'host.1600',
+                                       'Exec_Master_Log_Pos' => '77'
+                               ],
+                               [], // only a replica
+                               [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+                                       => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ],
+                               // replica/master use same var
+                               [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+                                       => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-77' ],
+                       ],
+                       [
+                               [
+                                       'gtid_executed' => '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-49,' .
+                                               '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77'
+                               ],
+                               [
+                                       'Relay_Master_Log_File' => 'host.1600',
+                                       'Exec_Master_Log_Pos' => '77'
+                               ],
+                               [], // only a replica
+                               [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+                                       => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ],
+                               // replica/master use same var
+                               [ '2E11FA47-71CA-11E1-9E33-C80AA9429562'
+                                       => '2E11FA47-71CA-11E1-9E33-C80AA9429562:51-77' ],
+                       ],
+                       [
+                               [
+                                       'gtid_executed' => null // not enabled?
+                               ],
+                               [
+                                       'Relay_Master_Log_File' => 'host.1600',
+                                       'Exec_Master_Log_Pos' => '77'
+                               ],
+                               [], // only a replica
+                               [], // binlog fallback
+                               false
+                       ],
+                       [
+                               [
+                                       'gtid_executed' => null // not enabled?
+                               ],
+                               [], // no replication
+                               [], // no replication
+                               false,
+                               false
+                       ]
+               ];
        }
 
        /**
@@ -510,4 +563,175 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
 
                $this->assertEquals( $pos, $roundtripPos );
        }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabaseMysqlBase::isInsertSelectSafe
+        * @dataProvider provideInsertSelectCases
+        */
+       public function testInsertSelectIsSafe( $insertOpts, $selectOpts, $row, $safe ) {
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'getReplicationSafetyInfo' ] )
+                       ->getMock();
+               $db->method( 'getReplicationSafetyInfo' )->willReturn( (object)$row );
+               $dbw = TestingAccessWrapper::newFromObject( $db );
+
+               $this->assertEquals( $safe, $dbw->isInsertSelectSafe( $insertOpts, $selectOpts ) );
+       }
+
+       public function provideInsertSelectCases() {
+               return [
+                       [
+                               [],
+                               [],
+                               [
+                                       'innodb_autoinc_lock_mode' => '2',
+                                       'binlog_format' => 'ROW',
+                               ],
+                               true
+                       ],
+                       [
+                               [],
+                               [ 'LIMIT' => 100 ],
+                               [
+                                       'innodb_autoinc_lock_mode' => '2',
+                                       'binlog_format' => 'ROW',
+                               ],
+                               true
+                       ],
+                       [
+                               [],
+                               [ 'LIMIT' => 100 ],
+                               [
+                                       'innodb_autoinc_lock_mode' => '0',
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               false
+                       ],
+                       [
+                               [],
+                               [],
+                               [
+                                       'innodb_autoinc_lock_mode' => '2',
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               false
+                       ],
+                       [
+                               [ 'NO_AUTO_COLUMNS' ],
+                               [ 'LIMIT' => 100 ],
+                               [
+                                       'innodb_autoinc_lock_mode' => '0',
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               false
+                       ],
+                       [
+                               [],
+                               [],
+                               [
+                                       'innodb_autoinc_lock_mode' => 0,
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               true
+                       ],
+                       [
+                               [ 'NO_AUTO_COLUMNS' ],
+                               [],
+                               [
+                                       'innodb_autoinc_lock_mode' => 2,
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               true
+                       ],
+                       [
+                               [ 'NO_AUTO_COLUMNS' ],
+                               [],
+                               [
+                                       'innodb_autoinc_lock_mode' => 0,
+                                       'binlog_format' => 'STATEMENT',
+                               ],
+                               true
+                       ],
+
+               ];
+       }
+
+       /**
+        * @covers \Wikimedia\Rdbms\DatabaseMysqlBase::buildIntegerCast
+        */
+       public function testBuildIntegerCast() {
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( null )
+                       ->getMock();
+               $output = $db->buildIntegerCast( 'fieldName' );
+               $this->assertSame( 'CAST( fieldName AS SIGNED )', $output );
+       }
+
+       /*
+        * @covers Wikimedia\Rdbms\Database::setIndexAliases
+        */
+       public function testIndexAliases() {
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'mysqlRealEscapeString' ] )
+                       ->getMock();
+               $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
+                       function ( $s ) {
+                               return str_replace( "'", "\\'", $s );
+                       }
+               );
+
+               $db->setIndexAliases( [ 'a_b_idx' => 'a_c_idx' ] );
+               $sql = $db->selectSQLText(
+                       'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] );
+
+               $this->assertEquals(
+                       "SELECT  field  FROM `zend`  FORCE INDEX (a_c_idx)  WHERE a = 'x'  ",
+                       $sql
+               );
+
+               $db->setIndexAliases( [] );
+               $sql = $db->selectSQLText(
+                       'zend', 'field', [ 'a' => 'x' ], __METHOD__, [ 'USE INDEX' => 'a_b_idx' ] );
+
+               $this->assertEquals(
+                       "SELECT  field  FROM `zend`  FORCE INDEX (a_b_idx)  WHERE a = 'x'  ",
+                       $sql
+               );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\Database::setTableAliases
+        */
+       public function testTableAliases() {
+               $db = $this->getMockBuilder( DatabaseMysqli::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [ 'mysqlRealEscapeString' ] )
+                       ->getMock();
+               $db->method( 'mysqlRealEscapeString' )->willReturnCallback(
+                       function ( $s ) {
+                               return str_replace( "'", "\\'", $s );
+                       }
+               );
+
+               $db->setTableAliases( [
+                       'meow' => [ 'dbname' => 'feline', 'schema' => null, 'prefix' => 'cat_' ]
+               ] );
+               $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ );
+
+               $this->assertEquals(
+                       "SELECT  field  FROM `feline`.`cat_meow`    WHERE a = 'x'  ",
+                       $sql
+               );
+
+               $db->setTableAliases( [] );
+               $sql = $db->selectSQLText( 'meow', 'field', [ 'a' => 'x' ], __METHOD__ );
+
+               $this->assertEquals(
+                       "SELECT  field  FROM `meow`    WHERE a = 'x'  ",
+                       $sql
+               );
+       }
 }