New toy: Automatic database upgrades (deactivated by default)
[lhc/web/wiklou.git] / includes / Upgrader.php
1 <?php
2 /*
3 A class to upgrade the database schema automagically
4 for MediaWIki itself, as well as for extensions
5 (c) 2006 by Magnus Manske
6 Released under GPL
7
8 USAGE:
9
10 First, copy AdminSettings.sample to AdminSettings.php and set the correct values there.
11 Then, set
12 $wgAllowUpgrader = true ;
13 in LocalSettings.php
14
15
16 In your extension, add something like:
17 $wgUpgrader->setUpgradeFunction ( "MyExtension" , "MyExtensionUpgradeFunction" ) ;
18
19 function MyExtensionUpgradeFunction ( &$upgrader ) {
20
21 # Does this installation need to be upgraded to V1.2.3.4?
22 if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.4" ) ) {
23 $upgrader->startTransaction() ;
24 # Do something using the database
25 # $upgrader->dba
26 $upgrader->endTransaction() ;
27 }
28
29 # Repeat this for every upgradable version, lowest version first, highest last
30 # if ( $upgrader->needsUpgrade ( "MyExtension" , "1.2.3.5" ) ) { ... }
31
32 }
33
34 The upgrade function for MediaWiki itself is at the end of Setup.php
35
36 */
37
38 class Upgrader {
39
40 var $data = array () ;
41 var $upgraders = array () ;
42 var $dba = NULL ; # Database, administration mode
43 var $data_loaded = false ;
44 var $db_transaction = false ;
45 var $dbr = NULL ;
46 var $dba = NULL ;
47
48 function loadVersionInfoFromDatabase () {
49 global $wgAllowUpgrader ;
50 if ( !$wgAllowUpgrader ) return ;
51 if ( $this->data_loaded ) return ;
52
53 $fname = "Upgrader::loadVersionInfoFromDatabase" ;
54 $this->dbr =& wfGetDB( DB_SLAVE );
55
56 # Upgrade to upgrader :-)
57 if ( !$this->dbr->tableExists ( "softwareversions" ) ) {
58 $this->prepareAdminDB () ;
59 $table = $this->dba->tableName( "softwareversions" );
60 $sql = "CREATE TABLE {$table} (
61 `sv_part` VARCHAR( 128 ) NOT NULL ,
62 `sv_version` VARCHAR( 128 ) NOT NULL ,
63 PRIMARY KEY ( `sv_part` )
64 );" ;
65 $this->dba->query ( $sql , $fname."-1" ) ;
66
67 # At this point, we wait until the table creation
68 # has progressed to the $dbr slave
69 while ( !$this->dbr->tableExists ( "softwareversions" ) ) {
70 sleep ( 1 ) ;
71 }
72 }
73
74 $res = $this->dbr->select (
75 "softwareversions" ,
76 "*" ,
77 array() ,
78 $fname."-2" ) ;
79
80 # Read existing versions
81 while ( $o = $this->dbr->fetchObject( $res ) ) {
82 $this->data[strtolower($o->sv_part)] = $this->makeVersionArray ( $o->sv_version ) ;
83 }
84 $this->dbr->freeResult( $res );
85
86 $this->data_loaded = true ;
87 }
88
89 /**
90 * Adds an upgrade function to the list, to be evoked by run() later
91 */
92 function setUpgradeFunction ( $part , $function ) {
93 $this->upgraders[$part] = $function ;
94 }
95
96 /**
97 * Evokes the upgrade functions
98 */
99 function run () {
100 global $wgAllowUpgrader ;
101 if ( !$wgAllowUpgrader ) return ;
102
103 # Running through the upgrading functions
104 foreach ( $this->upgraders AS $part => $function ) {
105 $function ( $this ) ;
106 }
107 }
108
109 /**
110 * Tells the caller if the given version is greater than the current one
111 @return bool TRUE if $part needs updating, FALSE otherwise
112 */
113 function needsUpgrade ( $part , $version ) {
114 $this->loadVersionInfoFromDatabase () ;
115 $currentversion = $this->getCurrentVersion ( $part ) ;
116 $this->lastcurrentversion = $currentversion ;
117 $this->lastpart = $part ;
118 $this->lastnewversion = $version ;
119 return $this->compareVersions ( $currentversion , $version ) ;
120 }
121
122 /**
123 * Initializes the transaction to update the database
124 */
125 function startTransaction () {
126 if ( $this->db_transaction ) # Already transaction in progress
127 return ;
128
129 # Open admin-access db
130 $this->prepareAdminDB() ;
131 $this->dba->begin() ; # Begin transaction
132 $this->db_transaction = true ;
133 }
134
135 /**
136 * Finalizes the transaction to update the database
137 * Does a last paranoia check
138 */
139 function endTransaction () {
140 if ( !$this->db_transaction )
141 return ;
142 if ( $this->dba == NULL )
143 return ;
144
145 $fname = "Upgrader::endTransaction" ;
146
147 if ( $this->stillCurrentVersion() ) {
148 # Version has not changed since initial lookup; so we're the only one updating, hopefully
149
150 # Set new version string
151 $v = $this->makeVersionArray ( $this->lastnewversion ) ;
152 $this->dba->delete (
153 "softwareversions",
154 array ( "sv_part" => $this->lastpart ) ,
155 $fname . "-DELETE"
156 ) ;
157 $this->dba->insert (
158 "softwareversions",
159 array (
160 "sv_part" => strtolower ( $this->lastpart ) ,
161 "sv_version" => implode ( "." , $v ) ,
162 ) ,
163 $fname . "-INSERT"
164 ) ;
165 $this->data[strtolower($this->lastpart)] = $v ;
166
167 $this->dba->commit($fname) ;
168 print $this->lastpart . " has been upgraded to version " . implode ( "." , $v ) . "<br/>\n" ;
169 } else {
170 # Version has changes, so someone probably did the update already
171 $this->dba->rollback($fname);
172 }
173 $this->db_transaction = false ;
174 }
175
176 /**
177 * Checks if the current version in the write table is still the same
178 * Multiple upgrading paranoia
179 @return bool
180 */
181 function stillCurrentVersion () {
182 # Check if this is really, *really* the correct version in the writing DB
183 # Using new database to go around the transaction
184 $dbw =& wfGetDB( DB_MASTER );
185 $res = $dbw->select (
186 "softwareversions" ,
187 "*" ,
188 array ( "sv_part" => $this->lastpart ) ,
189 "Upgrader::setLock" ) ;
190 $v = "0.0.0.0" ;
191 if ( $res != NULL ) {
192 if ( $o = $dbw->fetchObject( $res ) ) {
193 $dbw->freeResult( $res );
194 $v = $o->sv_version ;
195 }
196 }
197
198 # Are they unequal?
199 if ( $this->compareVersions ( $v , $this->lastcurrentversion ) OR
200 $this->compareVersions ( $this->lastcurrentversion , $v ) ) {
201 $this->error ( "Something funny happened on the way to the moon." ) ;
202 return false ;
203 }
204 return true ;
205 }
206
207 /**
208 * Open admin access to database
209 */
210 function prepareAdminDB () {
211 if ( $this->dba != NULL )
212 return ;
213
214 @include_once ( "AdminSettings.php" ) ;
215 if ( !isset ( $wgDBadminuser ) ) {
216 $this->error ( "You'll need to set up AdminSettings.php for automatic upgrades!" ) ;
217 exit ( 1 ) ;
218 }
219
220 global $wgDBserver , $wgDBname ;
221 $this->dba = new Database ( $wgDBserver , $wgDBadminuser , $wgDBadminpassword , $wgDBname ) ;
222 }
223
224 /**
225 * Get the current version of a part
226 @return array
227 */
228 function getCurrentVersion ( $part ) {
229 $part = strtolower ( $part ) ;
230 if ( isset ( $this->data[$part] ) ) {
231 return $this->data[$part] ;
232 } else {
233 # Not set yet, returning version 0.0.0.0
234 return $this->makeVersionArray ( "0.0.0.0" ) ;
235 }
236 }
237
238 /**
239 * Compares versions, style 1.2.3.4
240 * Takes arrays or strings (which will be automatically converted)
241 @return bool TRUE if $v1 < $v2
242 */
243 function compareVersions ( $v1 , $v2 ) {
244 if ( !is_array ( $v1 ) )
245 $v1 = $this->makeVersionArray ( $v1 ) ;
246 if ( !is_array ( $v2 ) )
247 $v2 = $this->makeVersionArray ( $v2 ) ;
248
249 # Compare versions
250 while ( count ( $v1 ) > 0 ) {
251 $a1 = array_shift ( $v1 ) ;
252 $a2 = array_shift ( $v2 ) ;
253 if ( $a1 < $a2 )
254 return true ;
255 if ( $a1 > $a2 )
256 return false ;
257 }
258 # Equal versions
259 return false ;
260 }
261
262 /**
263 * Converts a version string ("1.2.3.4") to an array
264 * Ensures exactly four elements (fills up with "0" if necessary)
265 @return array like [ "1" , "2" , "3" , "4" ]
266 */
267 function makeVersionArray ( $v ) {
268 # Convert to array
269 if ( !is_array ( $v ) ) {
270 $v = explode ( "." , $v ) ;
271 $ret = array () ;
272 foreach ( $v AS $x ) {
273 $x = trim ( $x ) ;
274 if ( !is_numeric ( $x ) )
275 continue ;
276 $ret[] = $x ;
277 }
278 } else {
279 $ret = $v ;
280 }
281
282 # Chop/stretch to length four
283 while ( count ( $ret ) > 4 )
284 array_pop ( $ret ) ;
285 while ( count ( $ret ) < 4 )
286 $ret[] = "0" ;
287 return $ret ;
288 }
289
290 /**
291 * Print an error and die a horrible death!
292 */
293 function error ( $e ) {
294 print $e . "<br/>\n" ;
295 exit ( 1 ) ;
296 }
297
298 } # End of class "Upgrader"
299
300 $wgUpgrader = new Upgrader ;
301
302
303 ?>