MySQL method to find out view + fix fatal in tests
authorAlephNull <drenfro@vistaprint.com>
Fri, 24 May 2013 15:14:40 +0000 (11:14 -0400)
committerHashar <hashar@free.fr>
Thu, 7 Nov 2013 09:43:59 +0000 (09:43 +0000)
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
RELEASE-NOTES-1.22
includes/db/Database.php
includes/db/DatabaseMysqlBase.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php

diff --git a/CREDITS b/CREDITS
index 23636ae..01505b0 100644 (file)
--- 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
index bc4de40..1c364ff 100644 (file)
@@ -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
index cd907e9..284fd6c 100644 (file)
@@ -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.
index 49579b6..26c9d24 100644 (file)
@@ -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 ) );
+       }
+
 }
 
 
index 8c849bc..6ce78b5 100644 (file)
@@ -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
index 134f856..ba63c09 100644 (file)
@@ -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!
+               );
+       }
+
 }