wiki.techinc.nl/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
Tim Starling 673d496f2d Have Database::addQuotes() pass through bare integers without quoting
Quotes started being added to integers in r4984 (August 2004). Before
that, is_numeric() was used to determine whether to add quotes, so
quotes were omitted from numeric strings, which is obviously wrong.

The idea here is to use the type of the variable to hint to the database
as to whether quotes are needed. The results are somewhat inconsistent,
since some callers do not convert numeric strings obtained from user
input to integers. That makes it a more conservative change. Callers can
opt out of unquoted integers by casting them to string.

The reason for doing this is that quoting integers turns out to be not
as harmless as originally assumed. We found a case of it confusing the
MariaDB query planner, causing inappropriate indexes to be used.

I also made addQuotes() consistently return a string, instead of
returning an integer for boolean values. This was already the case for
MySQL, but it seems like a good idea everywhere.

Bug: T238378
Change-Id: I70473280f542ee5ecd79e187f580807410fbd548
2019-11-18 11:40:28 +11:00

744 lines
25 KiB
PHP

<?php
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\DatabaseMysqli;
use Wikimedia\Rdbms\LBFactorySingle;
use Wikimedia\Rdbms\TransactionProfiler;
use Wikimedia\TestingAccessWrapper;
use Wikimedia\Rdbms\DatabaseSqlite;
use Wikimedia\Rdbms\DatabasePostgres;
use Wikimedia\Rdbms\DBUnexpectedError;
class DatabaseTest extends PHPUnit\Framework\TestCase {
/** @var DatabaseTestHelper */
private $db;
use MediaWikiCoversValidator;
protected function setUp() : void {
$this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
}
/**
* @dataProvider provideAddQuotes
* @covers Wikimedia\Rdbms\Database::factory
*/
public function testFactory() {
$m = Database::NEW_UNCONNECTED; // no-connect mode
$p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );
$x = $p + [ 'port' => 10000, 'UseWindowsAuth' => false ];
$x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
$x = $p + [ 'dbDirectory' => 'some/file' ];
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
}
public static function provideAddQuotes() {
return [
[ null, 'NULL' ],
[ 1234, "1234" ],
[ 1234.5678, "'1234.5678'" ],
[ 'string', "'string'" ],
[ 'string\'s cause trouble', "'string\'s cause trouble'" ],
];
}
/**
* @dataProvider provideAddQuotes
* @covers Wikimedia\Rdbms\Database::addQuotes
*/
public function testAddQuotes( $input, $expected ) {
$this->assertEquals( $expected, $this->db->addQuotes( $input ) );
}
public static function provideTableName() {
// Formatting is mostly ignored since addIdentifierQuotes is abstract.
// For testing of addIdentifierQuotes, see actual Database subclas tests.
return [
'local' => [
'tablename',
'tablename',
'quoted',
],
'local-raw' => [
'tablename',
'tablename',
'raw',
],
'shared' => [
'sharedb.tablename',
'tablename',
'quoted',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
],
'shared-raw' => [
'sharedb.tablename',
'tablename',
'raw',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
],
'shared-prefix' => [
'sharedb.sh_tablename',
'tablename',
'quoted',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
],
'shared-prefix-raw' => [
'sharedb.sh_tablename',
'tablename',
'raw',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
],
'foreign' => [
'databasename.tablename',
'databasename.tablename',
'quoted',
],
'foreign-raw' => [
'databasename.tablename',
'databasename.tablename',
'raw',
],
];
}
/**
* @dataProvider provideTableName
* @covers Wikimedia\Rdbms\Database::tableName
*/
public function testTableName( $expected, $table, $format, array $alias = null ) {
if ( $alias ) {
$this->db->setTableAliases( [ $table => $alias ] );
}
$this->assertEquals(
$expected,
$this->db->tableName( $table, $format ?: 'quoted' )
);
}
public function provideTableNamesWithIndexClauseOrJOIN() {
return [
'one-element array' => [
[ 'table' ], [], 'table '
],
'comma join' => [
[ 'table1', 'table2' ], [], 'table1,table2 '
],
'real join' => [
[ 'table1', 'table2' ],
[ 'table2' => [ 'LEFT JOIN', 't1_id = t2_id' ] ],
'table1 LEFT JOIN table2 ON ((t1_id = t2_id))'
],
'real join with multiple conditionals' => [
[ 'table1', 'table2' ],
[ 'table2' => [ 'LEFT JOIN', [ 't1_id = t2_id', 't2_x = \'X\'' ] ] ],
'table1 LEFT JOIN table2 ON ((t1_id = t2_id) AND (t2_x = \'X\'))'
],
'join with parenthesized group' => [
[ 'table1', 'n' => [ 'table2', 'table3' ] ],
[
'table3' => [ 'JOIN', 't2_id = t3_id' ],
'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
],
'table1 LEFT JOIN (table2 JOIN table3 ON ((t2_id = t3_id))) ON ((t1_id = t2_id))'
],
'join with degenerate parenthesized group' => [
[ 'table1', 'n' => [ 't2' => 'table2' ] ],
[
'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
],
'table1 LEFT JOIN table2 t2 ON ((t1_id = t2_id))'
],
];
}
/**
* @dataProvider provideTableNamesWithIndexClauseOrJOIN
* @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN
*/
public function testTableNamesWithIndexClauseOrJOIN( $tables, $join_conds, $expect ) {
$clause = TestingAccessWrapper::newFromObject( $this->db )
->tableNamesWithIndexClauseOrJOIN( $tables, [], [], $join_conds );
$this->assertSame( $expect, $clause );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle() {
$db = $this->db;
$db->clearFlag( DBO_TRX );
$called = false;
$flagSet = null;
$callback = function ( $trigger, IDatabase $db ) use ( &$flagSet, &$called ) {
$called = true;
$flagSet = $db->getFlag( DBO_TRX );
};
$db->onTransactionCommitOrIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
$flagSet = null;
$called = false;
$db->startAtomic( __METHOD__ );
$db->onTransactionCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Callback not reached during TRX' );
$db->endAtomic( __METHOD__ );
$this->assertTrue( $called, 'Callback reached after COMMIT' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$db->clearFlag( DBO_TRX );
$db->onTransactionCommitOrIdle(
function ( $trigger, IDatabase $db ) {
$db->setFlag( DBO_TRX );
},
__METHOD__
);
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle_TRX() {
$db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
$db->method( 'getDBname' )->willReturn( '' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
// Ask for the connection so that LB sets internal state
// about this connection being the master connection
$lb = $lbFactory->getMainLB();
$conn = $lb->openConnection( $lb->getWriterIndex() );
$this->assertSame( $db, $conn, 'Same DB instance' );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
$called = false;
$flagSet = null;
$callback = function () use ( $db, &$flagSet, &$called ) {
$called = true;
$flagSet = $db->getFlag( DBO_TRX );
};
$db->onTransactionCommitOrIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertTrue( $called, 'Called when lb-transaction is committed' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->rollbackMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called in next round commit' );
$db->setFlag( DBO_TRX );
try {
$db->onTransactionCommitOrIdle( function () {
throw new RuntimeException( 'test' );
} );
$this->fail( "Exception not thrown" );
} catch ( RuntimeException $e ) {
$this->assertTrue( $db->getFlag( DBO_TRX ) );
}
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->clearFlag( DBO_TRX );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX is not set' );
$called = false;
$db->onTransactionPreCommitOrIdle(
function ( IDatabase $db ) use ( &$called ) {
$called = true;
},
__METHOD__
);
$this->assertTrue( $called, 'Called when idle' );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionPreCommitOrIdle(
function ( IDatabase $db ) use ( &$called ) {
$called = true;
},
__METHOD__
);
$this->assertFalse( $called, 'Not called when transaction is active' );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Called when transaction is committed' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle_TRX() {
$db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
$db->method( 'getDBname' )->willReturn( 'unittest' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
// Ask for the connection so that LB sets internal state
// about this connection being the master connection
$lb = $lbFactory->getMainLB();
$conn = $lb->openConnection( $lb->getWriterIndex() );
$this->assertSame( $db, $conn, 'Same DB instance' );
$this->assertFalse( $lb->hasMasterChanges() );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
$called = false;
$callback = function ( IDatabase $db ) use ( &$called ) {
$called = true;
};
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
$called = false;
$lbFactory->commitMasterChanges();
$this->assertFalse( $called );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertTrue( $called, 'Called when lb-transaction is committed' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->rollbackMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called in next round commit' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionResolution
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionResolution() {
$db = $this->db;
$db->clearFlag( DBO_TRX );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) {
$called = true;
$db->setFlag( DBO_TRX );
} );
$db->commit( __METHOD__ );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$this->assertTrue( $called, 'Callback reached' );
$db->clearFlag( DBO_TRX );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) {
$called = true;
$db->setFlag( DBO_TRX );
} );
$db->rollback( __METHOD__ );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$this->assertTrue( $called, 'Callback reached' );
}
/**
* @covers Wikimedia\Rdbms\Database::setTransactionListener
*/
public function testTransactionListener() {
$db = $this->db;
$db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
$called = true;
} );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Callback still reached' );
$called = false;
$db->begin( __METHOD__ );
$db->rollback( __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$db->setTransactionListener( 'ping', null );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertFalse( $called, 'Callback not reached' );
}
/**
* Use this mock instead of DatabaseTestHelper for cases where
* DatabaseTestHelper is too inflexibile due to mocking too much
* or being too restrictive about fname matching (e.g. for tests
* that assert behaviour when the name is a mismatch, we need to
* catch the error here instead of there).
*
* @return Database
*/
private function getMockDB( $methods = [] ) {
static $abstractMethods = [
'fetchAffectedRowCount',
'closeConnection',
'dataSeek',
'doQuery',
'fetchObject', 'fetchRow',
'fieldInfo', 'fieldName',
'getSoftwareLink', 'getServerVersion',
'getType',
'indexInfo',
'insertId',
'lastError', 'lastErrno',
'numFields', 'numRows',
'open',
'strencode',
'tableExists'
];
$db = $this->getMockBuilder( Database::class )
->disableOriginalConstructor()
->setMethods( array_values( array_unique( array_merge(
$abstractMethods,
$methods
) ) ) )
->getMock();
$wdb = TestingAccessWrapper::newFromObject( $db );
$wdb->trxProfiler = new TransactionProfiler();
$wdb->connLogger = new \Psr\Log\NullLogger();
$wdb->queryLogger = new \Psr\Log\NullLogger();
$wdb->currentDomain = DatabaseDomain::newUnspecified();
return $db;
}
/**
* @covers Wikimedia\Rdbms\Database::flushSnapshot
*/
public function testFlushSnapshot() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->flushSnapshot( __METHOD__ ); // ok
$db->flushSnapshot( __METHOD__ ); // ok
$db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$db->query( 'SELECT 1', __METHOD__ );
$this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
$db->flushSnapshot( __METHOD__ ); // ok
$db->restoreFlags( $db::RESTORE_PRIOR );
$this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
}
/**
* @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush
* @covers Wikimedia\Rdbms\Database::lock
* @covers Wikimedia\Rdbms\Database::unlock
* @covers Wikimedia\Rdbms\Database::lockIsFree
*/
public function testGetScopedLock() {
$db = $this->getMockDB( [ 'isOpen', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'getDBname' )->willReturn( 'unittest' );
$this->assertSame( 0, $db->trxLevel() );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
$this->assertFalse( $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertSame( 0, $db->trxLevel() );
$db->setFlag( DBO_TRX );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
$this->assertFalse( $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$db->clearFlag( DBO_TRX );
// Pending writes with DBO_TRX
$this->assertSame( 0, $db->trxLevel() );
$this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
$db->setFlag( DBO_TRX );
$db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
try {
$lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
$this->fail( "Exception not reached" );
} catch ( DBUnexpectedError $e ) {
$this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
$this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ), 'Lock not acquired' );
}
$db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
// Pending writes without DBO_TRX
$db->clearFlag( DBO_TRX );
$this->assertSame( 0, $db->trxLevel() );
$this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ) );
$db->begin( __METHOD__ );
$db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
try {
$lock = $db->getScopedLockAndFlush( 'meow2', __METHOD__, 1 );
$this->fail( "Exception not reached" );
} catch ( DBUnexpectedError $e ) {
$this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
$this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ), 'Lock not acquired' );
}
$db->rollback( __METHOD__ );
// No pending writes, with DBO_TRX
$db->setFlag( DBO_TRX );
$this->assertSame( 0, $db->trxLevel() );
$this->assertTrue( $db->lockIsFree( 'wuff', __METHOD__ ) );
$db->query( "SELECT 1", __METHOD__ );
$this->assertEquals( 1, $db->trxLevel() );
$lock = $db->getScopedLockAndFlush( 'wuff', __METHOD__, 1 );
$this->assertSame( 0, $db->trxLevel() );
$this->assertFalse( $db->lockIsFree( 'wuff', __METHOD__ ), 'Lock already acquired' );
$db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
// No pending writes, without DBO_TRX
$db->clearFlag( DBO_TRX );
$this->assertSame( 0, $db->trxLevel() );
$this->assertTrue( $db->lockIsFree( 'wuff2', __METHOD__ ) );
$db->begin( __METHOD__ );
try {
$lock = $db->getScopedLockAndFlush( 'wuff2', __METHOD__, 1 );
$this->fail( "Exception not reached" );
} catch ( DBUnexpectedError $e ) {
$this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
$this->assertFalse( $db->lockIsFree( 'wuff2', __METHOD__ ), 'Lock not acquired' );
}
$db->rollback( __METHOD__ );
}
/**
* @covers Wikimedia\Rdbms\Database::getFlag
* @covers Wikimedia\Rdbms\Database::setFlag
* @covers Wikimedia\Rdbms\Database::restoreFlags
*/
public function testFlagSetting() {
$db = $this->db;
$origTrx = $db->getFlag( DBO_TRX );
$origNoBuffer = $db->getFlag( DBO_NOBUFFER );
$origTrx
? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
$origNoBuffer
? $db->clearFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR );
$this->assertEquals( !$origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
$db->restoreFlags( $db::RESTORE_INITIAL );
$this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
$this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
$origTrx
? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$origNoBuffer
? $db->clearFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR );
$db->restoreFlags();
$this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
$this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
$db->restoreFlags();
$this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
$this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
}
public function provideImmutableDBOFlags() {
return [
[ Database::DBO_IGNORE ],
[ Database::DBO_DEFAULT ],
[ Database::DBO_PERSISTENT ]
];
}
/**
* @covers Wikimedia\Rdbms\Database::setFlag
* @dataProvider provideImmutableDBOFlags
* @param int $flag
*/
public function testDBOCannotSet( $flag ) {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( null )
->getMock();
$this->expectException( DBUnexpectedError::class );
$db->setFlag( $flag );
}
/**
* @covers Wikimedia\Rdbms\Database::clearFlag
* @dataProvider provideImmutableDBOFlags
* @param int $flag
*/
public function testDBOCannotClear( $flag ) {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( null )
->getMock();
$this->expectException( DBUnexpectedError::class );
$db->clearFlag( $flag );
}
/**
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
public function testSchemaAndPrefixMutators() {
$ud = DatabaseDomain::newUnspecified();
$this->assertEquals( $ud->getId(), $this->db->getDomainID() );
$old = $this->db->tablePrefix();
$oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Prefix is string' );
$this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
$this->assertSame( $old, $this->db->tablePrefix( 'xxx_' ) );
$this->assertSame( 'xxx_', $this->db->tablePrefix(), "Prefix set" );
$this->db->tablePrefix( $old );
$this->assertNotEquals( 'xxx_', $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
$old = $this->db->dbSchema();
$oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Schema is string' );
$this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
$this->db->selectDB( 'y' );
$this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
$this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
$this->db->dbSchema( $old );
$this->assertNotEquals( 'xxx', $this->db->dbSchema() );
$this->assertSame( "y", $this->db->getDomainId() );
}
/**
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
public function testSchemaWithNoDB() {
$ud = DatabaseDomain::newUnspecified();
$this->assertEquals( $ud->getId(), $this->db->getDomainID() );
$this->assertSame( '', $this->db->dbSchema() );
$this->expectException( DBUnexpectedError::class );
$this->db->dbSchema( 'xxx' );
}
/**
* @covers Wikimedia\Rdbms\Database::selectDomain
*/
public function testSelectDomain() {
$oldDomain = $this->db->getDomainId();
$oldDatabase = $this->db->getDBname();
$oldSchema = $this->db->dbSchema();
$oldPrefix = $this->db->tablePrefix();
$this->db->selectDomain( 'testselectdb-xxx_' );
$this->assertSame( 'testselectdb', $this->db->getDBname() );
$this->assertSame( '', $this->db->dbSchema() );
$this->assertSame( 'xxx_', $this->db->tablePrefix() );
$this->db->selectDomain( $oldDomain );
$this->assertSame( $oldDatabase, $this->db->getDBname() );
$this->assertSame( $oldSchema, $this->db->dbSchema() );
$this->assertSame( $oldPrefix, $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
$this->db->selectDomain( 'testselectdb-schema-xxx_' );
$this->assertSame( 'testselectdb', $this->db->getDBname() );
$this->assertSame( 'schema', $this->db->dbSchema() );
$this->assertSame( 'xxx_', $this->db->tablePrefix() );
$this->db->selectDomain( $oldDomain );
$this->assertSame( $oldDatabase, $this->db->getDBname() );
$this->assertSame( $oldSchema, $this->db->dbSchema() );
$this->assertSame( $oldPrefix, $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
}
/**
* @covers Wikimedia\Rdbms\Database::getLBInfo
* @covers Wikimedia\Rdbms\Database::setLBInfo
*/
public function testGetSetLBInfo() {
$db = $this->getMockDB();
$this->assertEquals( [], $db->getLBInfo() );
$this->assertNull( $db->getLBInfo( 'pringles' ) );
$db->setLBInfo( 'soda', 'water' );
$this->assertEquals( [ 'soda' => 'water' ], $db->getLBInfo() );
$this->assertNull( $db->getLBInfo( 'pringles' ) );
$this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
$db->setLBInfo( 'basketball', 'Lebron' );
$this->assertEquals( [ 'soda' => 'water', 'basketball' => 'Lebron' ], $db->getLBInfo() );
$this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
$this->assertEquals( 'Lebron', $db->getLBInfo( 'basketball' ) );
$db->setLBInfo( 'soda', null );
$this->assertEquals( [ 'basketball' => 'Lebron' ], $db->getLBInfo() );
$db->setLBInfo( [ 'King' => 'James' ] );
$this->assertNull( $db->getLBInfo( 'basketball' ) );
$this->assertEquals( [ 'King' => 'James' ], $db->getLBInfo() );
}
}