gplx/mediawiki/extensions/SemanticMediaWiki/tests/phpunit/Utils/Connection/TestDatabaseTableBuilder.php

287 lines
6.4 KiB
PHP

<?php
namespace SMW\Tests\Utils\Connection;
use CloneDatabase;
use MediaWiki\MediaWikiServices;
use RuntimeException;
use SMW\Connection\ConnectionProvider;
use SMW\Store;
use SMW\Tests\Utils\PageCreator;
use Wikimedia\Rdbms\IDatabase;
/**
* @license GPL-2.0-or-later
* @since 2.0
*
* @author mwjames
*/
class TestDatabaseTableBuilder {
/**
* @var TestDatabaseTableBuilder
*/
private static $instance = null;
/**
* @var Store
*/
protected $store;
/**
* @var IConnectionProvider
*/
protected $connectionProvider;
/**
* @var CloneDatabase
*/
protected $cloneDatabase;
protected $supportedDatabaseTypes = [
'mysql',
'sqlite',
'postgres'
];
protected $availableDatabaseTypes = [];
private static $UTDB_PREFIX = null;
private static $MWDB_PREFIX = null;
private $dbSetup = false;
/**
* @since 2.0
*
* @param Store $store
* @param IConnectionProvider $connectionProvider
*/
public function __construct( Store $store, ConnectionProvider $connectionProvider ) {
$this->store = $store;
$this->connectionProvider = $connectionProvider;
$this->availableDatabaseTypes = $this->supportedDatabaseTypes;
self::$UTDB_PREFIX = 'sunittest_';
self::$MWDB_PREFIX = $GLOBALS['wgDBprefix'];
// MediaWikiTestCase changes the GLOBAL state therefore
// we have to ensure that we really use the original DB and
// not an existing 'unittest_' created by a MW test
if ( self::$MWDB_PREFIX === 'unittest_' ) {
self::$MWDB_PREFIX = '';
}
}
/**
* @since 2.0
*
* @param Store $store
*
* @return self
*/
public static function getInstance( Store $store ) {
if ( self::$instance === null ) {
self::$instance = new self( $store, new TestDatabaseConnectionProvider() );
}
return self::$instance;
}
/**
* @since 2.0
*
* @param string|null $databaseType
*
* @return self
*/
public function removeAvailableDatabaseType( $databaseType = null ) {
$this->availableDatabaseTypes = array_diff( $this->supportedDatabaseTypes, (array)$databaseType );
return $this;
}
/**
* @since 2.0
*
* @throws RuntimeException
*/
public function doBuild() {
if ( !$this->isAvailableDatabaseType() ) {
throw new RuntimeException( 'Requested DB type is not available through this installer' );
}
$this->setupDatabaseTables();
$this->rollbackOpenDatabaseTransactions();
}
/**
* @since 2.0
*/
public function doDestroy() {
$this->destroyDatabaseTables();
$this->rollbackOpenDatabaseTransactions();
}
/**
* @since 2.0
*
* @return string
*/
public function getDBPrefix() {
return self::$UTDB_PREFIX;
}
/**
* @since 2.0
*
* @return IDatabase
*/
public function getDBConnection() {
return $this->connectionProvider->getConnection();
}
/**
* @since 2.0
*
* @return ConnectionProvider
*/
public function getConnectionProvider() {
return $this->connectionProvider;
}
/**
* @see MediaWikiTestCase::listTables
*/
protected function generateListOfTables() {
$dbConnection = $this->getDBConnection();
$tables = $dbConnection->listTables(
self::$MWDB_PREFIX,
__METHOD__
);
// MW < 1.42
if (
version_compare( MW_VERSION, '1.42', '<' ) &&
$dbConnection->getType() === 'mysql' &&
method_exists( $dbConnection, 'listViews' )
) {
# bug 43571: cannot clone VIEWs under MySQL
$views = $dbConnection->listViews(
self::$MWDB_PREFIX,
__METHOD__
);
$tables = array_diff( $tables, $views );
}
$tables = array_map( [ $this, 'unprefixTable' ], $tables );
// Don't duplicate test tables from the previous failed run
$tables = array_filter( $tables, [ $this, 'isNotUnittest' ] );
// @see MediaWikiTestCase::listTables
if ( $dbConnection->getType() === 'sqlite' ) {
$tables = array_filter( $tables, [ $this, 'isNotSearchindex' ] );
}
return $tables;
}
private function setupDatabaseTables() {
if ( $this->dbSetup ) {
return true;
}
$this->cloneDatabaseTables();
$this->store->setup( false );
$this->createDummyPage();
$this->dbSetup = true;
}
private function cloneDatabaseTables() {
$tablesToBeCloned = $this->generateListOfTables();
// MW's DatabaseSqlite does some magic on its own therefore
// we force our way
if ( $this->getDBConnection()->getType() === 'sqlite' ) {
CloneDatabase::changePrefix( "" );
$this->cloneDatabase = new CloneDatabase(
$this->getDBConnection(),
$tablesToBeCloned,
""
);
} else {
$this->cloneDatabase = new CloneDatabase(
$this->getDBConnection(),
$tablesToBeCloned,
$this->getDBPrefix()
);
// Ensure no leftovers
if ( $this->getDBConnection()->getType() === 'postgres' ) {
$this->cloneDatabase->destroy( true );
}
// Rebuild the DB (in order to exclude temporary table usage)
// otherwise some tests will fail with
// "Error: 1137 Can't reopen table" on MySQL (see Issue #80)
$this->cloneDatabase->useTemporaryTables( false );
$this->cloneDatabase->cloneTableStructure();
// #3197
// @see https://github.com/wikimedia/mediawiki/commit/6badc7415684df54d6672098834359223b859507
CloneDatabase::changePrefix( self::$UTDB_PREFIX );
}
}
private function createDummyPage() {
$pageCreator = new PageCreator();
$pageCreator
->createPage( MediaWikiServices::getInstance()->getTitleFactory()->newFromText( 'SMWUTDummyPage' ) )
->doEdit( 'SMW dummy page' );
}
private function destroyDatabaseTables() {
if ( !$this->cloneDatabase instanceof CloneDatabase ) {
throw new RuntimeException( 'CloneDatabase instance is missing, unable to destory the database tables' );
}
$this->cloneDatabase->destroy( true );
$this->availableDatabaseTypes = $this->supportedDatabaseTypes;
$this->dbSetup = false;
}
private function rollbackOpenDatabaseTransactions() {
if ( $this->getDBConnection() ) {
while ( $this->getDBConnection()->trxLevel() > 0 ) {
$this->getDBConnection()->rollback();
}
// 1.26 was mde protected
// $this->getDBConnection()->ignoreErrors( false );
}
}
private function unprefixTable( $tableName ) {
return substr( $tableName, strlen( self::$MWDB_PREFIX ) );
}
private function isNotUnittest( $table ) {
return strpos( $table, 'unittest_' ) === false && strpos( $table, 'sunittest_' ) === false;
}
private function isNotSearchindex( $table ) {
return strpos( $table, 'searchindex' ) === false && strpos( $table, 'smw_ft_search' ) === false;
}
private function isAvailableDatabaseType() {
return in_array( $this->getDBConnection()->getType(), $this->availableDatabaseTypes );
}
}