* @covers \Wikimedia\Rdbms\LoadBalancer
*/
class LoadBalancerTest extends MediaWikiTestCase {
- private function makeServerConfig() {
+ private function makeServerConfig( $flags = DBO_DEFAULT ) {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
return [
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 0,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
+ 'flags' => $flags
];
}
$called = false;
$lb = new LoadBalancer( [
- 'servers' => [ $this->makeServerConfig() ],
+ // Simulate web request with DBO_TRX
+ 'servers' => [ $this->makeServerConfig( DBO_TRX ) ],
'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
'chronologyCallback' => function () use ( &$called ) {
$this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
$this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
$this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
-
$this->assertFalse( $called );
+
$dbw = $lb->getConnection( DB_MASTER );
$this->assertTrue( $called );
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
}
public function testWithReplica() {
- global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
-
- $servers = [
- [ // master
- 'host' => $wgDBserver,
- 'dbname' => $wgDBname,
- 'tablePrefix' => $this->dbPrefix(),
- 'user' => $wgDBuser,
- 'password' => $wgDBpassword,
- 'type' => $wgDBtype,
- 'dbDirectory' => $wgSQLiteDataDir,
- 'load' => 0,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
- ],
- [ // emulated replica
- 'host' => $wgDBserver,
- 'dbname' => $wgDBname,
- 'tablePrefix' => $this->dbPrefix(),
- 'user' => $wgDBuser,
- 'password' => $wgDBpassword,
- 'type' => $wgDBtype,
- 'dbDirectory' => $wgSQLiteDataDir,
- 'load' => 100,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
- ]
- ];
+ global $wgDBserver;
- $lb = new LoadBalancer( [
- 'servers' => $servers,
- 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
- 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
- 'loadMonitorClass' => LoadMonitorNull::class
- ] );
+ // Simulate web request with DBO_TRX
+ $lb = $this->newMultiServerLocalLoadBalancer( DBO_TRX );
$dbw = $lb->getConnection( DB_MASTER );
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
$lb->closeAll();
}
+ private function newSingleServerLocalLoadBalancer() {
+ global $wgDBname;
+
+ return new LoadBalancer( [
+ 'servers' => [ $this->makeServerConfig() ],
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
+ ] );
+ }
+
+ private function newMultiServerLocalLoadBalancer( $flags = DBO_DEFAULT ) {
+ global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
+
+ $servers = [
+ [ // master
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 0,
+ 'flags' => $flags
+ ],
+ [ // emulated replica
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 100,
+ 'flags' => $flags
+ ]
+ ];
+
+ return new LoadBalancer( [
+ 'servers' => $servers,
+ 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
+ 'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
+ 'loadMonitorClass' => LoadMonitorNull::class
+ ] );
+ }
+
private function assertWriteForbidden( Database $db ) {
try {
$db->delete( 'some_table', [ 'id' => 57634126 ], __METHOD__ );
* @covers LoadBalancer::getAnyOpenConnection()
*/
function testOpenConnection() {
- global $wgDBname;
-
- $lb = new LoadBalancer( [
- 'servers' => [ $this->makeServerConfig() ],
- 'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
- ] );
+ $lb = $this->newSingleServerLocalLoadBalancer();
$i = $lb->getWriterIndex();
$this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
+
$conn1 = $lb->getConnection( $i );
$this->assertNotEquals( null, $conn1 );
$this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
+ $this->assertFalse( $conn1->getFlag( DBO_TRX ) );
+
$conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
$this->assertNotEquals( null, $conn2 );
+ $this->assertFalse( $conn2->getFlag( DBO_TRX ) );
+
if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
$this->assertEquals( null,
$lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
$lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
$this->assertEquals( $conn2,
$lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ) );
+
+ $conn2->startAtomic( __METHOD__ );
+ try {
+ $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $conn2->endAtomic( __METHOD__ );
+ $this->fail( "No exception thrown." );
+ } catch ( DBUnexpectedError $e ) {
+ $this->assertEquals(
+ 'Wikimedia\Rdbms\LoadBalancer::openConnection: ' .
+ 'CONN_TRX_AUTOCOMMIT handle has a transaction.',
+ $e->getMessage()
+ );
+ }
+ $conn2->endAtomic( __METHOD__ );
}
$lb->closeAll();
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 0,
- 'flags' => DBO_TRX // REPEATABLE-READ for consistency
+ 'flags' => DBO_TRX // simulate a web request with DBO_TRX
],
];
$conn1->close();
$conn2->close();
}
+
+ public function testDBConnRefReadsMasterAndReplicaRoles() {
+ $lb = $this->newSingleServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+ $wConn = $lb->getConnectionRef( DB_MASTER );
+ $wConn2 = $lb->getConnectionRef( 0 );
+
+ $v = [ 'value' => '1', '1' ];
+ $sql = 'SELECT MAX(1) AS value';
+ foreach ( [ $rConn, $wConn, $wConn2 ] as $conn ) {
+ $conn->clearFlag( $conn::DBO_TRX );
+
+ $res = $conn->query( $sql, __METHOD__ );
+ $this->assertEquals( $v, $conn->fetchRow( $res ) );
+
+ $res = $conn->query( $sql, __METHOD__, $conn::QUERY_REPLICA_ROLE );
+ $this->assertEquals( $v, $conn->fetchRow( $res ) );
+ }
+
+ $wConn->getScopedLockAndFlush( 'key', __METHOD__, 1 );
+ $wConn2->getScopedLockAndFlush( 'key2', __METHOD__, 1 );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRole() {
+ $lb = $this->newSingleServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+
+ $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRoleIndex() {
+ $lb = $this->newMultiServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( 1 );
+
+ $rConn->query( 'DELETE FROM sometesttable WHERE 1=0' );
+ }
+
+ /**
+ * @expectedException \Wikimedia\Rdbms\DBReadOnlyRoleError
+ */
+ public function testDBConnRefWritesReplicaRoleInsert() {
+ $lb = $this->newMultiServerLocalLoadBalancer();
+
+ $rConn = $lb->getConnectionRef( DB_REPLICA );
+
+ $rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
+ }
}