MediaWiki REL1_34
ActorMigration.php
Go to the documentation of this file.
1<?php
26
39
45
54 private static $tempTables = [
55 'rev_user' => [
56 'table' => 'revision_actor_temp',
57 'pk' => 'revactor_rev',
58 'field' => 'revactor_actor',
59 'joinPK' => 'rev_id',
60 'extra' => [
61 'revactor_timestamp' => 'rev_timestamp',
62 'revactor_page' => 'rev_page',
63 ],
64 ],
65 ];
66
72 private static $formerTempTables = [];
73
79 private static $deprecated = [
80 'ar_user' => null, // 1.34
81 'img_user' => null, // 1.34
82 'oi_user' => null, // 1.34
83 'fa_user' => null, // 1.34
84 'rc_user' => null, // 1.34
85 'log_user' => null, // 1.34
86 'ipb_by' => null, // 1.34
87 ];
88
94 private static $removed = [];
95
101 private static $specialFields = [
102 'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
103 ];
104
106 private $joinCache = null;
107
109 private $stage;
110
112 public function __construct( $stage ) {
113 if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
114 throw new InvalidArgumentException( '$stage must include a write mode' );
115 }
116 if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
117 throw new InvalidArgumentException( '$stage must include a read mode' );
118 }
120 throw new InvalidArgumentException( 'Cannot read both schemas' );
121 }
123 throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
124 }
126 throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
127 }
128
129 $this->stage = $stage;
130 }
131
136 public static function newMigration() {
137 return MediaWikiServices::getInstance()->getActorMigration();
138 }
139
144 private static function checkDeprecation( $key ) {
145 if ( isset( self::$removed[$key] ) ) {
146 throw new InvalidArgumentException(
147 "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
148 );
149 }
150 if ( !empty( self::$deprecated[$key] ) ) {
151 wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
152 }
153 }
154
160 public function isAnon( $field ) {
161 return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
162 }
163
169 public function isNotAnon( $field ) {
170 return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
171 }
172
178 private static function getFieldNames( $key ) {
179 return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
180 }
181
194 public function getJoin( $key ) {
195 self::checkDeprecation( $key );
196
197 if ( !isset( $this->joinCache[$key] ) ) {
198 $tables = [];
199 $fields = [];
200 $joins = [];
201
202 list( $text, $actor ) = self::getFieldNames( $key );
203
204 if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
205 $fields[$key] = $key;
206 $fields[$text] = $text;
207 $fields[$actor] = 'NULL';
208 } else {
209 if ( isset( self::$tempTables[$key] ) ) {
210 $t = self::$tempTables[$key];
211 $alias = "temp_$key";
212 $tables[$alias] = $t['table'];
213 $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
214 $joinField = "{$alias}.{$t['field']}";
215 } else {
216 $joinField = $actor;
217 }
218
219 $alias = "actor_$key";
220 $tables[$alias] = 'actor';
221 $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
222
223 $fields[$key] = "{$alias}.actor_user";
224 $fields[$text] = "{$alias}.actor_name";
225 $fields[$actor] = $joinField;
226 }
227
228 $this->joinCache[$key] = [
229 'tables' => $tables,
230 'fields' => $fields,
231 'joins' => $joins,
232 ];
233 }
234
235 return $this->joinCache[$key];
236 }
237
247 public function getExistingActorId( IDatabase $db, UserIdentity $user ) {
248 $row = $db->selectRow(
249 'actor',
250 [ 'actor_id' ],
251 [ 'actor_name' => $user->getName() ],
252 __METHOD__
253 );
254 if ( $row === false ) {
255 return false;
256 }
257
258 return (int)$row->actor_id;
259 }
260
272 public function getNewActorId( IDatabase $dbw, UserIdentity $user ) {
273 $q = [
274 'actor_user' => $user->getId() ?: null,
275 'actor_name' => (string)$user->getName(),
276 ];
277 if ( $q['actor_user'] === null && User::isUsableName( $q['actor_name'] ) ) {
279 'Cannot create an actor for a usable name that is not an existing user: ' .
280 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
281 );
282 }
283 if ( $q['actor_name'] === '' ) {
285 'Cannot create an actor for a user with no name: ' .
286 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
287 );
288 }
289
290 $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
291
292 if ( $dbw->affectedRows() ) {
293 $actorId = (int)$dbw->insertId();
294 } else {
295 // Outdated cache?
296 // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
297 $actorId = (int)$dbw->selectField(
298 'actor',
299 'actor_id',
300 $q,
301 __METHOD__,
302 [ 'LOCK IN SHARE MODE' ]
303 );
304 if ( !$actorId ) {
306 "Failed to create actor ID for " .
307 "user_id={$user->getId()} user_name=\"{$user->getName()}\""
308 );
309 }
310 }
311
312 return $actorId;
313 }
314
324 public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
325 self::checkDeprecation( $key );
326
327 if ( isset( self::$tempTables[$key] ) ) {
328 throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
329 }
330
331 list( $text, $actor ) = self::getFieldNames( $key );
332 $ret = [];
333 if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
334 $ret[$key] = $user->getId();
335 $ret[$text] = $user->getName();
336 }
337 if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
338 // NOTE: Don't use $user->getActorId(), since that may be for the wrong wiki (T260485)
339 // TODO: Make User object wiki-aware and let it handle all cases (T260933)
340 $existingActorId = $this->getExistingActorId( $dbw, $user );
341 if ( $existingActorId !== false ) {
342 $ret[$actor] = $existingActorId;
343 } else {
344 $ret[$actor] = $this->getNewActorId( $dbw, $user );
345 }
346 }
347 return $ret;
348 }
349
362 public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
363 self::checkDeprecation( $key );
364
365 if ( isset( self::$formerTempTables[$key] ) ) {
366 wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
367 } elseif ( !isset( self::$tempTables[$key] ) ) {
368 throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
369 }
370
371 list( $text, $actor ) = self::getFieldNames( $key );
372 $ret = [];
373 $callback = null;
374 if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
375 $ret[$key] = $user->getId();
376 $ret[$text] = $user->getName();
377 }
378 if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
379 // We need to be able to assign an actor ID if none exists
380 if ( !$user instanceof User && !$user->getActorId() ) {
381 $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
382 }
383 $id = $user->getActorId( $dbw );
384
385 if ( isset( self::$tempTables[$key] ) ) {
386 $func = __METHOD__;
387 $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
388 $t = self::$tempTables[$key];
389 $set = [ $t['field'] => $id ];
390 foreach ( $t['extra'] as $to => $from ) {
391 if ( !array_key_exists( $from, $extra ) ) {
392 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
393 }
394 $set[$to] = $extra[$from];
395 }
396 $dbw->upsert(
397 $t['table'],
398 [ $t['pk'] => $pk ] + $set,
399 [ $t['pk'] ],
400 $set,
401 $func
402 );
403 };
404 } else {
405 $ret[$actor] = $id;
406 $callback = function ( $pk, array $extra ) {
407 };
408 }
409 } elseif ( isset( self::$tempTables[$key] ) ) {
410 $func = __METHOD__;
411 $callback = function ( $pk, array $extra ) use ( $key, $func ) {
412 $t = self::$tempTables[$key];
413 foreach ( $t['extra'] as $to => $from ) {
414 if ( !array_key_exists( $from, $extra ) ) {
415 throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
416 }
417 }
418 };
419 } else {
420 $callback = function ( $pk, array $extra ) {
421 };
422 }
423 return [ $ret, $callback ];
424 }
425
447 public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
448 self::checkDeprecation( $key );
449
450 $tables = [];
451 $conds = [];
452 $joins = [];
453
454 if ( $users instanceof UserIdentity ) {
455 $users = [ $users ];
456 }
457
458 // Get information about all the passed users
459 $ids = [];
460 $names = [];
461 $actors = [];
462 foreach ( $users as $user ) {
463 if ( $useId && $user->getId() ) {
464 $ids[] = $user->getId();
465 } else {
466 $names[] = $user->getName();
467 }
468 $actorId = $user->getActorId();
469 if ( $actorId ) {
470 $actors[] = $actorId;
471 }
472 }
473
474 list( $text, $actor ) = self::getFieldNames( $key );
475
476 // Combine data into conditions to be ORed together
477 if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
478 if ( $actors ) {
479 if ( isset( self::$tempTables[$key] ) ) {
480 $t = self::$tempTables[$key];
481 $alias = "temp_$key";
482 $tables[$alias] = $t['table'];
483 $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
484 $joinField = "{$alias}.{$t['field']}";
485 } else {
486 $joinField = $actor;
487 }
488 $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
489 }
490 } else {
491 if ( $ids ) {
492 $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
493 }
494 if ( $names ) {
495 $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
496 }
497 }
498
499 return [
500 'tables' => $tables,
501 'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
502 'orconds' => $conds,
503 'joins' => $joins,
504 ];
505 }
506
507}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
This class handles the logic for the actor table migration.
const MIGRATION_STAGE_SCHEMA_COMPAT
Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage (in MW <1....
static array $formerTempTables
Fields that formerly used $tempTables Key is '$key', value is the MediaWiki version in which it was r...
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
static array $tempTables
Define fields that use temporary tables for transitional purposes Keys are '$key',...
static array $specialFields
Define fields that use non-standard mapping Keys are the user id column name, values are arrays with ...
static newMigration()
Static constructor.
array null $joinCache
Cache for self::getJoin()
static checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
getNewActorId(IDatabase $dbw, UserIdentity $user)
Attempt to assign an actor ID to the given user.
int $stage
Combination of SCHEMA_COMPAT_* constants.
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
getExistingActorId(IDatabase $db, UserIdentity $user)
Get actor ID from UserIdentity, if it exists.
static string[] $removed
Define fields that are removed for use with this class.
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
static string null[] $deprecated
Define fields that are deprecated for use with this class.
getJoin( $key)
Get SELECT fields and joins for the actor key.
static getFieldNames( $key)
Exception thrown when an actor can't be created.
MediaWikiServices is the service locator for the application scope of MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
const SCHEMA_COMPAT_WRITE_BOTH
Definition Defines.php:277
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:276
const SCHEMA_COMPAT_READ_BOTH
Definition Defines.php:278
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:273
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:274
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:275
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
affectedRows()
Get the number of rows affected by the last write query.
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
upsert( $table, array $rows, $uniqueIndexes, array $set, $fname=__METHOD__)
INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
insertId()
Get the inserted value of an auto-increment row.