New toy: Automatic database upgrades (deactivated by default)
authorMagnus Manske <magnusmanske@users.mediawiki.org>
Tue, 10 Jan 2006 12:53:43 +0000 (12:53 +0000)
committerMagnus Manske <magnusmanske@users.mediawiki.org>
Tue, 10 Jan 2006 12:53:43 +0000 (12:53 +0000)
Can create/upgrade database tables for MediaWiki and extensions based on internal version management.
For HOWTO see the beginning of include/Upgrader.php

includes/DefaultSettings.php
includes/Setup.php
includes/Upgrader.php [new file with mode: 0644]

index feb430e..1f8103d 100644 (file)
@@ -1812,4 +1812,10 @@ $wgFilterRobotsWL = false;
  */
 $wgAllowCategorizedRecentChanges = false ;
 
+/**
+ * Include upgrader
+ */
+$wgAllowUpgrader = false ;
+require_once ( "Upgrader.php" ) ;
+
 ?>
index 12bc6f6..5fcda81 100644 (file)
@@ -246,6 +246,7 @@ $wgMessageCache->initialise( $parserMemc, $wgUseDatabaseMessages, $wgMsgCacheExp
 
 wfProfileOut( $fname.'-MessageCache' );
 
+
 #
 # I guess the warning about UI switching might still apply...
 #
@@ -311,6 +312,30 @@ foreach ( $wgExtensionFunctions as $func ) {
 wfDebug( "\n" );
 $wgFullyInitialised = true;
 wfProfileOut( $fname.'-extensions' );
+
+
+
+wfProfileIn( $fname.'-upgrades' );
+
+# Upgrade MediaWiki
+$wgUpgrader->setUpgradeFunction ( "MediaWiki" , "wfMediaWikiUpgrade" ) ;
+
+function wfMediaWikiUpgrade ( &$upgrader ) {
+
+       if ( $upgrader->needsUpgrade ( "MediaWiki" , "1.6" ) ) {
+               $upgrader->startTransaction() ;
+               # Do something
+               $upgrader->endTransaction() ;
+       }
+
+}
+
+
+# Upgrade software, if necessary
+$wgUpgrader->run() ;
+
+wfProfileOut( $fname.'-upgrades' );
+
 wfProfileOut( $fname );
 
 }
diff --git a/includes/Upgrader.php b/includes/Upgrader.php
new file mode 100644 (file)
index 0000000..171cf6a
--- /dev/null
@@ -0,0 +1,303 @@
+<?php
+/*
+A class to upgrade the database schema automagically
+for MediaWIki itself, as well as for extensions
+(c) 2006 by Magnus Manske
+Released under GPL
+
+USAGE:
+
+First, copy AdminSettings.sample to AdminSettings.php and set the correct values there.
+Then, set
+       $wgAllowUpgrader = true ;
+in LocalSettings.php
+
+
+In your extension, add something like:
+$wgUpgrader->setUpgradeFunction ( "MyExtension" , "MyExtensionUpgradeFunction" ) ;
+
+function MyExtensionUpgradeFunction ( &$upgrader ) {
+
+       # Does this installation need to be upgraded to V1.2.3.4?
+       if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.4" ) ) {
+               $upgrader->startTransaction() ;
+               # Do something using the database
+               # $upgrader->dba
+               $upgrader->endTransaction() ;
+       }
+       
+       # Repeat this for every upgradable version, lowest version first, highest last
+       # if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.5" ) ) { ... }
+       
+}
+
+The upgrade function for MediaWiki itself is at the end of Setup.php
+
+*/
+
+class Upgrader {
+       
+       var $data = array () ;
+       var $upgraders = array () ;
+       var $dba = NULL ; # Database, administration mode
+       var $data_loaded = false ;
+       var $db_transaction = false ;
+       var $dbr = NULL ;
+       var $dba = NULL ;
+       
+       function loadVersionInfoFromDatabase () {
+               global $wgAllowUpgrader ;
+               if ( !$wgAllowUpgrader ) return ;
+               if ( $this->data_loaded ) return ;
+               
+               $fname = "Upgrader::loadVersionInfoFromDatabase" ;
+               $this->dbr =& wfGetDB( DB_SLAVE );
+               
+               # Upgrade to upgrader :-)
+               if ( !$this->dbr->tableExists ( "softwareversions" ) ) {
+                       $this->prepareAdminDB () ;
+                       $table = $this->dba->tableName( "softwareversions" );
+                       $sql = "CREATE TABLE {$table} (
+                               `sv_part` VARCHAR( 128 ) NOT NULL ,
+                               `sv_version` VARCHAR( 128 ) NOT NULL ,
+                               PRIMARY KEY ( `sv_part` )
+                       );" ;
+                       $this->dba->query ( $sql , $fname."-1" ) ;
+                       
+                       # At this point, we wait until the table creation
+                       # has progressed to the $dbr slave
+                       while ( !$this->dbr->tableExists ( "softwareversions" ) ) {
+                               sleep ( 1 ) ;
+                       }
+               }
+               
+               $res = $this->dbr->select (
+                       "softwareversions" ,
+                       "*" ,
+                       array() ,
+                       $fname."-2" ) ;
+               
+               # Read existing versions
+               while ( $o = $this->dbr->fetchObject( $res ) ) {
+                       $this->data[strtolower($o->sv_part)] = $this->makeVersionArray ( $o->sv_version ) ;
+               }
+               $this->dbr->freeResult( $res );
+               
+               $this->data_loaded = true ;
+       }
+       
+       /**
+        * Adds an upgrade function to the list, to be evoked by run() later
+        */
+       function setUpgradeFunction ( $part , $function ) {
+               $this->upgraders[$part] = $function ;
+       }
+       
+       /**
+        * Evokes the upgrade functions
+        */
+       function run () {
+               global $wgAllowUpgrader ;
+               if ( !$wgAllowUpgrader ) return ;
+
+               # Running through the upgrading functions
+               foreach ( $this->upgraders AS $part => $function ) {
+                       $function ( $this ) ;
+               }
+       }
+       
+       /**
+        * Tells the caller if the given version is greater than  the current one
+        @return bool TRUE if $part needs updating, FALSE otherwise
+        */
+       function needsUpgrade ( $part , $version ) {
+               $this->loadVersionInfoFromDatabase () ;
+               $currentversion = $this->getCurrentVersion ( $part ) ;
+               $this->lastcurrentversion = $currentversion ;
+               $this->lastpart = $part ;
+               $this->lastnewversion = $version ;
+               return $this->compareVersions ( $currentversion , $version ) ;
+       }
+       
+       /**
+        * Initializes the transaction to update the database
+        */
+       function startTransaction () {
+               if ( $this->db_transaction ) # Already transaction in progress
+                       return ;
+
+               # Open admin-access db
+               $this->prepareAdminDB() ;
+               $this->dba->begin() ; # Begin transaction
+               $this->db_transaction = true ;
+       }
+       
+       /**
+        * Finalizes the transaction to update the database
+        * Does a last paranoia check
+        */
+       function endTransaction () {
+               if ( !$this->db_transaction )
+                       return ;
+               if ( $this->dba == NULL )
+                       return ;
+
+               $fname = "Upgrader::endTransaction" ;
+               
+               if ( $this->stillCurrentVersion() ) {
+                       # Version has not changed since initial lookup; so we're the only one updating, hopefully
+
+                       # Set new version string
+                       $v = $this->makeVersionArray ( $this->lastnewversion ) ;
+                       $this->dba->delete (
+                               "softwareversions",
+                               array ( "sv_part" => $this->lastpart ) ,
+                               $fname . "-DELETE"
+                       ) ;
+                       $this->dba->insert (
+                               "softwareversions",
+                               array (
+                                       "sv_part" => strtolower ( $this->lastpart ) ,
+                                       "sv_version" => implode ( "." , $v ) ,
+                               ) ,
+                               $fname . "-INSERT"
+                       ) ;
+                       $this->data[strtolower($this->lastpart)] = $v ;
+
+                       $this->dba->commit($fname) ;
+                       print $this->lastpart . " has been upgraded to version " . implode ( "." , $v ) . "<br/>\n" ;
+               } else {
+                       # Version has changes, so someone probably did the update already
+                       $this->dba->rollback($fname);
+               }
+               $this->db_transaction = false ;
+       }
+
+       /**
+        * Checks if the current version in the write table is still the same
+        * Multiple upgrading paranoia
+        @return bool
+        */
+       function stillCurrentVersion () {
+               # Check if this is really, *really* the correct version in the writing DB
+               # Using new database to go around the transaction
+               $dbw =& wfGetDB( DB_MASTER );
+               $res = $dbw->select (
+                       "softwareversions" ,
+                       "*" ,
+                       array ( "sv_part" => $this->lastpart ) ,
+                       "Upgrader::setLock" ) ;
+               $v = "0.0.0.0" ;
+               if ( $res != NULL ) {
+                       if ( $o = $dbw->fetchObject( $res ) ) {
+                               $dbw->freeResult( $res );
+                               $v = $o->sv_version ;
+                               }
+               }
+               
+               # Are they unequal?
+               if ( $this->compareVersions ( $v , $this->lastcurrentversion ) OR
+                        $this->compareVersions ( $this->lastcurrentversion , $v ) ) {
+                       $this->error ( "Something funny happened on the way to the moon." ) ;
+                       return false ;
+               }
+               return true ;
+       }
+       
+       /**
+        * Open admin access to database
+        */
+       function prepareAdminDB () {
+               if ( $this->dba != NULL )
+                       return ;
+               
+               @include_once ( "AdminSettings.php" ) ;
+               if ( !isset ( $wgDBadminuser ) ) {
+                       $this->error ( "You'll need to set up AdminSettings.php for automatic upgrades!" ) ;
+                       exit ( 1 ) ;
+               }
+               
+               global $wgDBserver , $wgDBname ;
+               $this->dba = new Database ( $wgDBserver , $wgDBadminuser , $wgDBadminpassword , $wgDBname ) ;
+       }
+
+       /**
+        * Get the current version of a part
+        @return array
+        */
+       function getCurrentVersion ( $part ) {
+               $part = strtolower ( $part ) ;
+               if ( isset ( $this->data[$part] ) ) {
+                       return $this->data[$part] ;
+               } else {
+                       # Not set yet, returning version 0.0.0.0
+                       return $this->makeVersionArray ( "0.0.0.0" ) ;
+               }
+       }
+       
+       /**
+        * Compares versions, style 1.2.3.4
+        * Takes arrays or strings (which will be automatically converted)
+        @return bool TRUE if $v1 < $v2
+        */
+       function compareVersions ( $v1 , $v2 ) {
+               if ( !is_array ( $v1 ) )
+                       $v1 = $this->makeVersionArray ( $v1 ) ;
+               if ( !is_array ( $v2 ) )
+                       $v2 = $this->makeVersionArray ( $v2 ) ;
+                       
+               # Compare versions
+               while ( count ( $v1 ) > 0 ) {
+                       $a1 = array_shift ( $v1 ) ;
+                       $a2 = array_shift ( $v2 ) ;
+                       if ( $a1 < $a2 )
+                               return true ;
+                       if ( $a1 > $a2 )
+                               return false ;
+               }
+               # Equal versions
+               return false ;
+       }
+       
+       /**
+        * Converts a version string ("1.2.3.4") to an array
+        * Ensures exactly four elements (fills up with "0" if necessary)
+        @return array like [ "1" , "2" , "3" , "4" ]
+        */
+       function makeVersionArray ( $v ) {
+               # Convert to array
+               if ( !is_array ( $v ) ) {
+                       $v = explode ( "." , $v ) ;
+                       $ret = array () ;
+                       foreach ( $v AS $x ) {
+                               $x = trim ( $x ) ;
+                               if ( !is_numeric ( $x ) )
+                                       continue ;
+                               $ret[] = $x ;
+                       }
+               } else {
+                       $ret = $v ;
+               }
+               
+               # Chop/stretch to length four
+               while ( count ( $ret ) > 4 )
+                       array_pop ( $ret ) ;
+               while ( count ( $ret ) < 4 )
+                       $ret[] = "0" ;
+               return $ret ;
+       }
+       
+       /**
+        * Print an error and die a horrible death!
+        */
+       function error ( $e ) {
+               print $e . "<br/>\n" ;
+               exit ( 1 ) ;
+       }
+       
+} # End of class "Upgrader"
+
+$wgUpgrader = new Upgrader ;
+
+
+?>
\ No newline at end of file