From e9d53667f43230e84302d0a6653ea7e78a7841ce Mon Sep 17 00:00:00 2001 From: AlephNull Date: Fri, 24 May 2013 11:14:40 -0400 Subject: [PATCH] MySQL method to find out view + fix fatal in tests We were missing a method to list out views defined in a database. This patch adds in MysqlBase::isView() and MysqlBse::listViews(). Since listViews() cache its result in DatabaseBase::$allViews, we also introduce a final DatabaseBase::clearViewsCache() to let us clear the per process cache. Finally, fixed fatal error when duplicating VIEWs in MySQL. bug: 43571 Change-Id: I8650baa4b721fe69ea3e1d557dd76745c0c7754e --- CREDITS | 1 + RELEASE-NOTES-1.22 | 3 + includes/db/Database.php | 40 +++++++++ includes/db/DatabaseMysqlBase.php | 49 +++++++++++ tests/phpunit/MediaWikiTestCase.php | 6 ++ .../includes/db/DatabaseMysqlBaseTest.php | 83 +++++++++++++++++++ 6 files changed, 182 insertions(+) diff --git a/CREDITS b/CREDITS index 23636aebc6..01505b0af6 100644 --- a/CREDITS +++ b/CREDITS @@ -22,6 +22,7 @@ following names for their contribution to the product. * church of emacs * Daniel Friesen * Daniel Kinzler +* Daniel Renfro * Danny B. * David McCabe * Derk-Jan Hartman diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index bc4de402ce..1c364ffefc 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -260,6 +260,9 @@ production. Previously in the "SimpleAntiSpam" extension by Ryan Schmidt. * populateRevisionLength.php maintenance script updated to also populate archive.ar_len field. +* (bug 43571) DatabaseMySQLBase learned to list views, optionally filtered by a + prefix. Also fixed PHPUnit test suite when using a MySQL backend containing + views. === Bug fixes in 1.22 === * (bug 47271) $wgContentHandlerUseDB should be set to false during the upgrade diff --git a/includes/db/Database.php b/includes/db/Database.php index cd907e9a69..284fd6c8be 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -301,6 +301,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { */ protected $fileHandle = null; + /** + * @since 1.22 + * @var Process cache of VIEWs names in the database + */ + protected $allViews = null; + # ------------------------------------------------------------------------------ # Accessors # ------------------------------------------------------------------------------ @@ -3444,6 +3450,40 @@ abstract class DatabaseBase implements IDatabase, DatabaseType { throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); } + /** + * Reset the views process cache set by listViews() + * @since 1.22 + */ + final public function clearViewsCache() { + $this->allViews = null; + } + + /** + * Lists all the VIEWs in the database + * + * For caching purposes the list of all views should be stored in + * $this->allViews. The process cache can be cleared with clearViewsCache() + * + * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ + * @param string $fname Name of calling function + * @throws MWException + * @since 1.22 + */ + public function listViews( $prefix = null, $fname = __METHOD__ ) { + throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' ); + } + + /** + * Differentiates between a TABLE and a VIEW + * + * @param $name string: Name of the database-structure to test. + * @throws MWException + * @since 1.22 + */ + public function isView( $name ) { + throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' ); + } + /** * Convert a timestamp in one of the formats accepted by wfTimestamp() * to the format used for inserting into timestamp fields in this DBMS. diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php index 49579b6a97..26c9d247d2 100644 --- a/includes/db/DatabaseMysqlBase.php +++ b/includes/db/DatabaseMysqlBase.php @@ -996,6 +996,55 @@ abstract class DatabaseMysqlBase extends DatabaseBase { return $status; } + /** + * Lists VIEWs in the database + * + * @param string $prefix Only show VIEWs with this prefix, eg. + * unit_test_, or $wgDBprefix. Default: null, would return all views. + * @param string $fname Name of calling function + * @return array + * @since 1.22 + */ + public function listViews( $prefix = null, $fname = __METHOD__ ) { + + if ( !isset( $this->allViews ) ) { + + // The name of the column containing the name of the VIEW + $propertyName = 'Tables_in_' . $this->mDBname; + + // Query for the VIEWS + $result = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' ); + $this->allViews = array(); + while ( ($row = $this->fetchRow($result)) !== false ) { + array_push( $this->allViews, $row[$propertyName] ); + } + } + + if ( is_null($prefix) || $prefix === '' ) { + return $this->allViews; + } + + $filteredViews = array(); + foreach ( $this->allViews as $viewName ) { + // Does the name of this VIEW start with the table-prefix? + if ( strpos( $viewName, $prefix ) === 0 ) { + array_push( $filteredViews, $viewName ); + } + } + return $filteredViews; + } + + /** + * Differentiates between a TABLE and a VIEW. + * + * @param $name string: Name of the TABLE/VIEW to test + * @return bool + * @since 1.22 + */ + public function isView( $name, $prefix = null ) { + return in_array( $name, $this->listViews( $prefix ) ); + } + } diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 8c849bcefc..6ce78b5664 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -537,6 +537,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { global $wgDBprefix; $tables = $db->listTables( $wgDBprefix, __METHOD__ ); + + if ( $db->getType() === 'mysql' ) { + # bug 43571: cannot clone VIEWs under MySQL + $views = $db->listViews( $wgDBprefix, __METHOD__ ); + $tables = array_diff( $tables, $views ); + } $tables = array_map( array( __CLASS__, 'unprefixTable' ), $tables ); // Don't duplicate test tables from the previous fataled run diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php index 134f856601..ba63c09195 100644 --- a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -123,4 +123,87 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase { return json_decode( '"' . $str . '"' ); } + function getMockForViews() { + $db = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->setMethods( array( 'fetchRow', 'query' ) ) + ->getMock(); + + $db->expects( $this->any() ) + ->method( 'query' ) + ->with( $this->anything() ) + ->will( + $this->returnValue( null ) + ); + + $db->expects( $this->any() ) + ->method( 'fetchRow' ) + ->with( $this->anything() ) + ->will( $this->onConsecutiveCalls( + array( 'Tables_in_' => 'view1' ), + array( 'Tables_in_' => 'view2' ), + array( 'Tables_in_' => 'myview' ), + false # no more rows + )); + return $db; + } + /** + * @covers DatabaseMysqlBase::listViews + */ + function testListviews() { + $db = $this->getMockForViews(); + + // The first call populate an internal cache of views + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + + // Prefix filtering + $this->assertEquals( array( 'view1', 'view2' ), + $db->listViews( 'view' ) ); + $this->assertEquals( array( 'myview' ), + $db->listViews( 'my' ) ); + $this->assertEquals( array(), + $db->listViews( 'UNUSED_PREFIX' ) ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews( '' ) ); + } + + /** + * @covers DatabaseMysqlBase::isView + * @dataProvider provideViewExistanceChecks + */ + function testIsView( $isView, $viewName ) { + $db = $this->getMockForViews(); + + switch( $isView ) { + case true: + $this->assertTrue( $db->isView( $viewName ), + "$viewName should be considered a view" ); + break; + + case false: + $this->assertFalse( $db->isView( $viewName ), + "$viewName has not been defined as a view" ); + break; + } + + } + + function provideViewExistanceChecks() { + return array( + // format: whether it is a view, view name + array( true, 'view1' ), + array( true, 'view2' ), + array( true, 'myview' ), + + array( false, 'user' ), + + array( false, 'view10' ), + array( false, 'my' ), + array( false, 'OH_MY_GOD' ), # they killed kenny! + ); + } + } -- 2.20.1