MediaWiki  master
ActorMigration.php
Go to the documentation of this file.
1 <?php
28 
42 
48 
57  private static $tempTables = [
58  'rev_user' => [
59  'table' => 'revision_actor_temp',
60  'pk' => 'revactor_rev',
61  'field' => 'revactor_actor',
62  'joinPK' => 'rev_id',
63  'extra' => [
64  'revactor_timestamp' => 'rev_timestamp',
65  'revactor_page' => 'rev_page',
66  ],
67  ],
68  ];
69 
75  private static $formerTempTables = [];
76 
82  private static $deprecated = [
83  'ar_user' => null, // 1.34
84  'img_user' => null, // 1.34
85  'oi_user' => null, // 1.34
86  'fa_user' => null, // 1.34
87  'rc_user' => null, // 1.34
88  'log_user' => null, // 1.34
89  'ipb_by' => null, // 1.34
90  ];
91 
97  private static $removed = [];
98 
104  private static $specialFields = [
105  'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
106  ];
107 
109  private $joinCache = [];
110 
112  private $stage;
113 
115  private $userFactory;
116 
118  private $userNameUtils;
119 
126  public function __construct(
127  $stage,
130  ) {
131  if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
132  throw new InvalidArgumentException( '$stage must include a write mode' );
133  }
134  if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
135  throw new InvalidArgumentException( '$stage must include a read mode' );
136  }
138  throw new InvalidArgumentException( 'Cannot read both schemas' );
139  }
141  throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
142  }
144  throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
145  }
146 
147  $this->stage = $stage;
148  $this->userFactory = $userFactory;
149  $this->userNameUtils = $userNameUtils;
150  }
151 
156  public static function newMigration() {
157  return MediaWikiServices::getInstance()->getActorMigration();
158  }
159 
164  private static function checkDeprecation( $key ) {
165  if ( isset( self::$removed[$key] ) ) {
166  throw new InvalidArgumentException(
167  "Use of " . static::class . " for '$key' was removed in MediaWiki " . self::$removed[$key]
168  );
169  }
170  if ( !empty( self::$deprecated[$key] ) ) {
171  wfDeprecated( static::class . " for '$key'", self::$deprecated[$key], false, 3 );
172  }
173  }
174 
180  public function isAnon( $field ) {
181  return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
182  }
183 
189  public function isNotAnon( $field ) {
190  return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
191  }
192 
198  private static function getFieldNames( $key ) {
199  return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
200  }
201 
214  public function getJoin( $key ) {
215  self::checkDeprecation( $key );
216 
217  if ( !isset( $this->joinCache[$key] ) ) {
218  $tables = [];
219  $fields = [];
220  $joins = [];
221 
222  list( $text, $actor ) = self::getFieldNames( $key );
223 
224  if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
225  $fields[$key] = $key;
226  $fields[$text] = $text;
227  $fields[$actor] = 'NULL';
228  } else {
229  if ( isset( self::$tempTables[$key] ) ) {
230  $t = self::$tempTables[$key];
231  $alias = "temp_$key";
232  $tables[$alias] = $t['table'];
233  $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
234  $joinField = "{$alias}.{$t['field']}";
235  } else {
236  $joinField = $actor;
237  }
238 
239  $alias = "actor_$key";
240  $tables[$alias] = 'actor';
241  $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
242 
243  $fields[$key] = "{$alias}.actor_user";
244  $fields[$text] = "{$alias}.actor_name";
245  $fields[$actor] = $joinField;
246  }
247 
248  $this->joinCache[$key] = [
249  'tables' => $tables,
250  'fields' => $fields,
251  'joins' => $joins,
252  ];
253  }
254 
255  return $this->joinCache[$key];
256  }
257 
267  public function getExistingActorId( IDatabase $db, UserIdentity $user ) {
268  $row = $db->selectRow(
269  'actor',
270  [ 'actor_id' ],
271  [ 'actor_name' => $user->getName() ],
272  __METHOD__
273  );
274  if ( $row === false ) {
275  return false;
276  }
277 
278  return (int)$row->actor_id;
279  }
280 
292  public function getNewActorId( IDatabase $dbw, UserIdentity $user ) {
293  $q = [
294  'actor_user' => $user->getId() ?: null,
295  'actor_name' => (string)$user->getName(),
296  ];
297  if ( $q['actor_user'] === null && $this->userNameUtils->isUsable( $q['actor_name'] ) ) {
298  throw new CannotCreateActorException(
299  'Cannot create an actor for a usable name that is not an existing user: ' .
300  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
301  );
302  }
303  if ( $q['actor_name'] === '' ) {
304  throw new CannotCreateActorException(
305  'Cannot create an actor for a user with no name: ' .
306  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
307  );
308  }
309 
310  $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
311 
312  if ( $dbw->affectedRows() ) {
313  $actorId = (int)$dbw->insertId();
314  } else {
315  // Outdated cache?
316  // Use LOCK IN SHARE MODE to bypass any MySQL REPEATABLE-READ snapshot.
317  $actorId = (int)$dbw->selectField(
318  'actor',
319  'actor_id',
320  $q,
321  __METHOD__,
322  [ 'LOCK IN SHARE MODE' ]
323  );
324  if ( !$actorId ) {
325  throw new CannotCreateActorException(
326  "Failed to create actor ID for " .
327  "user_id={$user->getId()} user_name=\"{$user->getName()}\""
328  );
329  }
330  }
331 
332  return $actorId;
333  }
334 
344  public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
345  self::checkDeprecation( $key );
346 
347  if ( isset( self::$tempTables[$key] ) ) {
348  throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
349  }
350 
351  list( $text, $actor ) = self::getFieldNames( $key );
352  $ret = [];
353  if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
354  $ret[$key] = $user->getId();
355  $ret[$text] = $user->getName();
356  }
357  if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
358  // NOTE: Don't use $user->getActorId(), since that may be for the wrong wiki (T260485)
359  // TODO: Make User object wiki-aware and let it handle all cases (T260933)
360  $existingActorId = $this->getExistingActorId( $dbw, $user );
361  if ( $existingActorId !== false ) {
362  $ret[$actor] = $existingActorId;
363  } else {
364  $ret[$actor] = $this->getNewActorId( $dbw, $user );
365  }
366  }
367  return $ret;
368  }
369 
382  public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
383  self::checkDeprecation( $key );
384 
385  if ( isset( self::$formerTempTables[$key] ) ) {
386  wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
387  } elseif ( !isset( self::$tempTables[$key] ) ) {
388  throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
389  }
390 
391  list( $text, $actor ) = self::getFieldNames( $key );
392  $ret = [];
393  $callback = null;
394  if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
395  $ret[$key] = $user->getId();
396  $ret[$text] = $user->getName();
397  }
398  if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
399  // We need to be able to assign an actor ID if none exists
400  if ( !$user instanceof User && !$user->getActorId() ) {
401  $user = $this->userFactory->newFromAnyId(
402  $user->getId(),
403  $user->getName(),
404  null
405  );
406  }
407  $id = $user->getActorId( $dbw );
408 
409  if ( isset( self::$tempTables[$key] ) ) {
410  $func = __METHOD__;
411  $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
412  $t = self::$tempTables[$key];
413  $set = [ $t['field'] => $id ];
414  foreach ( $t['extra'] as $to => $from ) {
415  if ( !array_key_exists( $from, $extra ) ) {
416  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
417  }
418  $set[$to] = $extra[$from];
419  }
420  $dbw->upsert(
421  $t['table'],
422  [ $t['pk'] => $pk ] + $set,
423  [ [ $t['pk'] ] ],
424  $set,
425  $func
426  );
427  };
428  } else {
429  $ret[$actor] = $id;
430  $callback = function ( $pk, array $extra ) {
431  };
432  }
433  } elseif ( isset( self::$tempTables[$key] ) ) {
434  $func = __METHOD__;
435  $callback = function ( $pk, array $extra ) use ( $key, $func ) {
436  $t = self::$tempTables[$key];
437  foreach ( $t['extra'] as $to => $from ) {
438  if ( !array_key_exists( $from, $extra ) ) {
439  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
440  }
441  }
442  };
443  } else {
444  $callback = function ( $pk, array $extra ) {
445  };
446  }
447  return [ $ret, $callback ];
448  }
449 
473  public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
474  self::checkDeprecation( $key );
475 
476  $tables = [];
477  $conds = [];
478  $joins = [];
479 
480  if ( $users instanceof UserIdentity ) {
481  $users = [ $users ];
482  } elseif ( $users === null || $users === false ) {
483  // DWIM
484  $users = [];
485  } elseif ( !is_array( $users ) ) {
486  $what = is_object( $users ) ? get_class( $users ) : gettype( $users );
487  throw new InvalidArgumentException(
488  __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
489  );
490  }
491 
492  // Get information about all the passed users
493  $ids = [];
494  $names = [];
495  $actors = [];
496  foreach ( $users as $user ) {
497  if ( $useId && $user->getId() ) {
498  $ids[] = $user->getId();
499  } else {
500  $names[] = $user->getName();
501  }
502  $actorId = $user->getActorId();
503  if ( $actorId ) {
504  $actors[] = $actorId;
505  }
506  }
507 
508  list( $text, $actor ) = self::getFieldNames( $key );
509 
510  // Combine data into conditions to be ORed together
511  if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
512  if ( $actors ) {
513  if ( isset( self::$tempTables[$key] ) ) {
514  $t = self::$tempTables[$key];
515  $alias = "temp_$key";
516  $tables[$alias] = $t['table'];
517  $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
518  $joinField = "{$alias}.{$t['field']}";
519  } else {
520  $joinField = $actor;
521  }
522  $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
523  }
524  } else {
525  if ( $ids ) {
526  $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
527  }
528  if ( $names ) {
529  $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
530  }
531  }
532 
533  return [
534  'tables' => $tables,
535  'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
536  'orconds' => $conds,
537  'joins' => $joins,
538  ];
539  }
540 
541 }
ActorMigration\$userFactory
UserFactory $userFactory
Definition: ActorMigration.php:115
ActorMigration\MIGRATION_STAGE_SCHEMA_COMPAT
const MIGRATION_STAGE_SCHEMA_COMPAT
Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage (in MW <1....
Definition: ActorMigration.php:47
Wikimedia\Rdbms\IDatabase\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
SCHEMA_COMPAT_READ_NEW
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:277
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:165
ActorMigration\$removed
static string[] $removed
Define fields that are removed for use with this class.
Definition: ActorMigration.php:97
Wikimedia\Rdbms\IDatabase\upsert
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert the given row(s) into a table.
ActorMigration\__construct
__construct( $stage, UserFactory $userFactory, UserNameUtils $userNameUtils)
Definition: ActorMigration.php:126
ActorMigration\$joinCache
array $joinCache
Cache for self::getJoin()
Definition: ActorMigration.php:109
ActorMigration
This class handles the logic for the actor table migration and should always be used in lieu of direc...
Definition: ActorMigration.php:41
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:156
MediaWiki\User\UserIdentity\getActorId
getActorId()
LIST_AND
const LIST_AND
Definition: Defines.php:48
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
ActorMigration\isAnon
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
Definition: ActorMigration.php:180
ActorMigration\getFieldNames
static getFieldNames( $key)
Definition: ActorMigration.php:198
LIST_OR
const LIST_OR
Definition: Defines.php:51
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1027
ActorMigration\checkDeprecation
static checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
Definition: ActorMigration.php:164
ActorMigration\$stage
int $stage
Combination of SCHEMA_COMPAT_* constants.
Definition: ActorMigration.php:112
MediaWiki\User\UserIdentity\getName
getName()
ActorMigration\getExistingActorId
getExistingActorId(IDatabase $db, UserIdentity $user)
Get actor ID from UserIdentity, if it exists.
Definition: ActorMigration.php:267
ActorMigration\isNotAnon
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
Definition: ActorMigration.php:189
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
SCHEMA_COMPAT_WRITE_OLD
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:274
CannotCreateActorException
Exception thrown when an actor can't be created.
Definition: CannotCreateActorException.php:29
ActorMigration\$specialFields
static array $specialFields
Define fields that use non-standard mapping Keys are the user id column name, values are arrays with ...
Definition: ActorMigration.php:104
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:276
ActorMigration\getWhere
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
Definition: ActorMigration.php:473
ActorMigration\getJoin
getJoin( $key)
Get SELECT fields and joins for the actor key.
Definition: ActorMigration.php:214
ActorMigration\getInsertValuesWithTempTable
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigration.php:382
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert the given row(s) into a table.
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
ActorMigration\$tempTables
static array $tempTables
Define fields that use temporary tables for transitional purposes Keys are '$key',...
Definition: ActorMigration.php:57
SCHEMA_COMPAT_WRITE_BOTH
const SCHEMA_COMPAT_WRITE_BOTH
Definition: Defines.php:278
ActorMigration\$userNameUtils
UserNameUtils $userNameUtils
Definition: ActorMigration.php:118
$t
$t
Definition: testCompression.php:74
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
ActorMigration\$deprecated
static string null[] $deprecated
Define fields that are deprecated for use with this class.
Definition: ActorMigration.php:82
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
ActorMigration\getInsertValues
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigration.php:344
SCHEMA_COMPAT_READ_BOTH
const SCHEMA_COMPAT_READ_BOTH
Definition: Defines.php:279
Wikimedia\Rdbms\IDatabase\makeList
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
MediaWiki\User\UserFactory
Creates User objects.
Definition: UserFactory.php:40
SCHEMA_COMPAT_READ_OLD
const SCHEMA_COMPAT_READ_OLD
Definition: Defines.php:275
ActorMigration\getNewActorId
getNewActorId(IDatabase $dbw, UserIdentity $user)
Attempt to assign an actor ID to the given user.
Definition: ActorMigration.php:292
ActorMigration\$formerTempTables
static array $formerTempTables
Fields that formerly used $tempTables Key is '$key', value is the MediaWiki version in which it was r...
Definition: ActorMigration.php:75