When this was originally written, the plan was to read both the old and new fields during the transition period, while stopping writes to them midway through. It turns out that the WHERE conditions to do read-both correctly are generally not handled well by the database and working around that would require a lot of complicated code (see what's being removed from ApiQueryUserContribs here, for example). We can simplify things greatly by instead having it write both fields during the transition period, reading from the old for the first part and the new for the second part, as is being done for MCR. Bug: T204669 Change-Id: I4764c1c7883dc1003cb12729455c8107319f70b1 Depends-On: I845f6ae462f2539ebd35cbb5f2ca8b5714e2c1fb Depends-On: I88b31b977543fdbdf69f8c1158e77e448df94e11
735 lines
22 KiB
PHP
735 lines
22 KiB
PHP
<?php
|
|
|
|
use MediaWiki\User\UserIdentity;
|
|
use MediaWiki\MediaWikiServices;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group Database
|
|
* @covers ActorMigration
|
|
*/
|
|
class ActorMigrationTest extends MediaWikiLangTestCase {
|
|
|
|
protected $tablesUsed = [
|
|
'revision',
|
|
'revision_actor_temp',
|
|
'ipblocks',
|
|
'recentchanges',
|
|
'actor',
|
|
];
|
|
|
|
/**
|
|
* Create an ActorMigration for a particular stage
|
|
* @param int $stage
|
|
* @return ActorMigration
|
|
*/
|
|
protected function makeMigration( $stage ) {
|
|
return new ActorMigration( $stage );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideConstructor
|
|
* @param int $stage
|
|
* @param string|null $exceptionMsg
|
|
*/
|
|
public function testConstructor( $stage, $exceptionMsg ) {
|
|
try {
|
|
$m = new ActorMigration( $stage );
|
|
if ( $exceptionMsg !== null ) {
|
|
$this->fail( 'Expected exception not thrown' );
|
|
}
|
|
$this->assertInstanceOf( ActorMigration::class, $m );
|
|
} catch ( InvalidArgumentException $ex ) {
|
|
$this->assertSame( $exceptionMsg, $ex->getMessage() );
|
|
}
|
|
}
|
|
|
|
public static function provideConstructor() {
|
|
return [
|
|
[ 0, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
|
|
[ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
|
|
[
|
|
SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW,
|
|
'Cannot read the new schema without also writing it'
|
|
],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
|
|
[
|
|
SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD,
|
|
'Cannot read the old schema without also writing it'
|
|
],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
|
|
|
|
[ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetJoin
|
|
* @param int $stage
|
|
* @param string $key
|
|
* @param array $expect
|
|
*/
|
|
public function testGetJoin( $stage, $key, $expect ) {
|
|
$m = $this->makeMigration( $stage );
|
|
$result = $m->getJoin( $key );
|
|
$this->assertEquals( $expect, $result );
|
|
}
|
|
|
|
public static function provideGetJoin() {
|
|
return [
|
|
'Simple table, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rc_user', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rc_user' => 'rc_user',
|
|
'rc_user_text' => 'rc_user_text',
|
|
'rc_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rc_user' => 'rc_user',
|
|
'rc_user_text' => 'rc_user_text',
|
|
'rc_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', [
|
|
'tables' => [ 'actor_rc_user' => 'actor' ],
|
|
'fields' => [
|
|
'rc_user' => 'actor_rc_user.actor_user',
|
|
'rc_user_text' => 'actor_rc_user.actor_name',
|
|
'rc_actor' => 'rc_actor',
|
|
],
|
|
'joins' => [
|
|
'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
|
|
],
|
|
],
|
|
],
|
|
'Simple table, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rc_user', [
|
|
'tables' => [ 'actor_rc_user' => 'actor' ],
|
|
'fields' => [
|
|
'rc_user' => 'actor_rc_user.actor_user',
|
|
'rc_user_text' => 'actor_rc_user.actor_name',
|
|
'rc_actor' => 'rc_actor',
|
|
],
|
|
'joins' => [
|
|
'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
|
|
],
|
|
],
|
|
],
|
|
|
|
'ipblocks, old' => [
|
|
SCHEMA_COMPAT_OLD, 'ipb_by', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'ipb_by' => 'ipb_by',
|
|
'ipb_by_text' => 'ipb_by_text',
|
|
'ipb_by_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'ipblocks, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'ipb_by' => 'ipb_by',
|
|
'ipb_by_text' => 'ipb_by_text',
|
|
'ipb_by_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'ipblocks, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', [
|
|
'tables' => [ 'actor_ipb_by' => 'actor' ],
|
|
'fields' => [
|
|
'ipb_by' => 'actor_ipb_by.actor_user',
|
|
'ipb_by_text' => 'actor_ipb_by.actor_name',
|
|
'ipb_by_actor' => 'ipb_by_actor',
|
|
],
|
|
'joins' => [
|
|
'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
|
|
],
|
|
],
|
|
],
|
|
'ipblocks, new' => [
|
|
SCHEMA_COMPAT_NEW, 'ipb_by', [
|
|
'tables' => [ 'actor_ipb_by' => 'actor' ],
|
|
'fields' => [
|
|
'ipb_by' => 'actor_ipb_by.actor_user',
|
|
'ipb_by_text' => 'actor_ipb_by.actor_name',
|
|
'ipb_by_actor' => 'ipb_by_actor',
|
|
],
|
|
'joins' => [
|
|
'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
|
|
],
|
|
],
|
|
],
|
|
|
|
'Revision, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rev_user', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rev_user' => 'rev_user',
|
|
'rev_user_text' => 'rev_user_text',
|
|
'rev_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', [
|
|
'tables' => [],
|
|
'fields' => [
|
|
'rev_user' => 'rev_user',
|
|
'rev_user_text' => 'rev_user_text',
|
|
'rev_actor' => 'NULL',
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', [
|
|
'tables' => [
|
|
'temp_rev_user' => 'revision_actor_temp',
|
|
'actor_rev_user' => 'actor',
|
|
],
|
|
'fields' => [
|
|
'rev_user' => 'actor_rev_user.actor_user',
|
|
'rev_user_text' => 'actor_rev_user.actor_name',
|
|
'rev_actor' => 'temp_rev_user.revactor_actor',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
|
|
'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
|
|
],
|
|
],
|
|
],
|
|
'Revision, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rev_user', [
|
|
'tables' => [
|
|
'temp_rev_user' => 'revision_actor_temp',
|
|
'actor_rev_user' => 'actor',
|
|
],
|
|
'fields' => [
|
|
'rev_user' => 'actor_rev_user.actor_user',
|
|
'rev_user_text' => 'actor_rev_user.actor_name',
|
|
'rev_actor' => 'temp_rev_user.revactor_actor',
|
|
],
|
|
'joins' => [
|
|
'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
|
|
'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetWhere
|
|
* @param int $stage
|
|
* @param string $key
|
|
* @param UserIdentity[] $users
|
|
* @param bool $useId
|
|
* @param array $expect
|
|
*/
|
|
public function testGetWhere( $stage, $key, $users, $useId, $expect ) {
|
|
$expect['conds'] = '(' . implode( ') OR (', $expect['orconds'] ) . ')';
|
|
|
|
if ( count( $users ) === 1 ) {
|
|
$users = reset( $users );
|
|
}
|
|
|
|
$m = $this->makeMigration( $stage );
|
|
$result = $m->getWhere( $this->db, $key, $users, $useId );
|
|
$this->assertEquals( $expect, $result );
|
|
}
|
|
|
|
public function provideGetWhere() {
|
|
$makeUserIdentity = function ( $id, $name, $actor ) {
|
|
$u = $this->getMock( UserIdentity::class );
|
|
$u->method( 'getId' )->willReturn( $id );
|
|
$u->method( 'getName' )->willReturn( $name );
|
|
$u->method( 'getActorId' )->willReturn( $actor );
|
|
return $u;
|
|
};
|
|
|
|
$genericUser = [ $makeUserIdentity( 1, 'User1', 11 ) ];
|
|
$complicatedUsers = [
|
|
$makeUserIdentity( 1, 'User1', 11 ),
|
|
$makeUserIdentity( 2, 'User2', 12 ),
|
|
$makeUserIdentity( 3, 'User3', 0 ),
|
|
$makeUserIdentity( 0, '192.168.12.34', 34 ),
|
|
$makeUserIdentity( 0, '192.168.12.35', 0 ),
|
|
];
|
|
|
|
return [
|
|
'Simple table, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rc_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "rc_user = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "rc_user = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor = '11'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Simple table, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rc_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor = '11'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
|
|
'ipblocks, old' => [
|
|
SCHEMA_COMPAT_OLD, 'ipb_by', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "ipb_by = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'ipblocks, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "ipb_by = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'ipblocks, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'ipblocks, new' => [
|
|
SCHEMA_COMPAT_NEW, 'ipb_by', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
|
|
'Revision, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rev_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "rev_user = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', $genericUser, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'userid' => "rev_user = '1'" ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Revision, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', $genericUser, true, [
|
|
'tables' => [
|
|
'temp_rev_user' => 'revision_actor_temp',
|
|
],
|
|
'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
|
|
'joins' => [
|
|
'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
|
|
],
|
|
],
|
|
],
|
|
'Revision, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rev_user', $genericUser, true, [
|
|
'tables' => [
|
|
'temp_rev_user' => 'revision_actor_temp',
|
|
],
|
|
'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
|
|
'joins' => [
|
|
'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
|
|
],
|
|
],
|
|
],
|
|
|
|
'Multiple users, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, true, [
|
|
'tables' => [],
|
|
'orconds' => [
|
|
'userid' => "rc_user IN ('1','2','3') ",
|
|
'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, true, [
|
|
'tables' => [],
|
|
'orconds' => [
|
|
'userid' => "rc_user IN ('1','2','3') ",
|
|
'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, true, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
|
|
'Multiple users, no use ID, old' => [
|
|
SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, false, [
|
|
'tables' => [],
|
|
'orconds' => [
|
|
'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, read-old' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, false, [
|
|
'tables' => [],
|
|
'orconds' => [
|
|
'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
|
|
],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, false, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
'Multiple users, new' => [
|
|
SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, false, [
|
|
'tables' => [],
|
|
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
|
|
'joins' => [],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideInsertRoundTrip
|
|
* @param string $table
|
|
* @param string $key
|
|
* @param string $pk
|
|
* @param array $extraFields
|
|
*/
|
|
public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
|
|
$u = $this->getTestUser()->getUser();
|
|
$user = $this->getMock( UserIdentity::class );
|
|
$user->method( 'getId' )->willReturn( $u->getId() );
|
|
$user->method( 'getName' )->willReturn( $u->getName() );
|
|
if ( $u->getActorId( $this->db ) ) {
|
|
$user->method( 'getActorId' )->willReturn( $u->getActorId() );
|
|
} else {
|
|
$this->db->insert(
|
|
'actor',
|
|
[ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
|
|
__METHOD__
|
|
);
|
|
$user->method( 'getActorId' )->willReturn( $this->db->insertId() );
|
|
}
|
|
|
|
$stageNames = [
|
|
SCHEMA_COMPAT_OLD => 'old',
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => 'write-both-read-old',
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => 'write-both-read-new',
|
|
SCHEMA_COMPAT_NEW => 'new',
|
|
];
|
|
|
|
$stages = [
|
|
SCHEMA_COMPAT_OLD => [
|
|
SCHEMA_COMPAT_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
|
|
],
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
|
|
SCHEMA_COMPAT_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
|
|
SCHEMA_COMPAT_NEW
|
|
],
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
|
|
SCHEMA_COMPAT_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
|
|
SCHEMA_COMPAT_NEW
|
|
],
|
|
SCHEMA_COMPAT_NEW => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
|
|
SCHEMA_COMPAT_NEW
|
|
],
|
|
];
|
|
|
|
$nameKey = $key . '_text';
|
|
$actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
|
|
|
|
foreach ( $stages as $writeStage => $possibleReadStages ) {
|
|
if ( $key === 'ipb_by' ) {
|
|
$extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
|
|
}
|
|
|
|
$w = $this->makeMigration( $writeStage );
|
|
$usesTemp = $key === 'rev_user';
|
|
|
|
if ( $usesTemp ) {
|
|
list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db, $key, $user );
|
|
} else {
|
|
$fields = $w->getInsertValues( $this->db, $key, $user );
|
|
}
|
|
|
|
if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
|
|
$this->assertSame( $user->getId(), $fields[$key],
|
|
"old field, stage={$stageNames[$writeStage]}" );
|
|
$this->assertSame( $user->getName(), $fields[$nameKey],
|
|
"old field, stage={$stageNames[$writeStage]}" );
|
|
} else {
|
|
$this->assertArrayNotHasKey( $key, $fields, "old field, stage={$stageNames[$writeStage]}" );
|
|
$this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage={$stageNames[$writeStage]}" );
|
|
}
|
|
if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
|
|
$this->assertSame( $user->getActorId(), $fields[$actorKey],
|
|
"new field, stage={$stageNames[$writeStage]}" );
|
|
} else {
|
|
$this->assertArrayNotHasKey( $actorKey, $fields,
|
|
"new field, stage={$stageNames[$writeStage]}" );
|
|
}
|
|
|
|
$this->db->insert( $table, $extraFields + $fields, __METHOD__ );
|
|
$id = $this->db->insertId();
|
|
if ( $usesTemp ) {
|
|
$callback( $id, $extraFields );
|
|
}
|
|
|
|
foreach ( $possibleReadStages as $readStage ) {
|
|
$r = $this->makeMigration( $readStage );
|
|
|
|
$queryInfo = $r->getJoin( $key );
|
|
$row = $this->db->selectRow(
|
|
[ $table ] + $queryInfo['tables'],
|
|
$queryInfo['fields'],
|
|
[ $pk => $id ],
|
|
__METHOD__,
|
|
[],
|
|
$queryInfo['joins']
|
|
);
|
|
|
|
$this->assertSame( $user->getId(), (int)$row->$key,
|
|
"w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, id" );
|
|
$this->assertSame( $user->getName(), $row->$nameKey,
|
|
"w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, name" );
|
|
$this->assertSame(
|
|
( $readStage & SCHEMA_COMPAT_READ_OLD ) ? 0 : $user->getActorId(),
|
|
(int)$row->$actorKey,
|
|
"w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, actor"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function provideInsertRoundTrip() {
|
|
$db = wfGetDB( DB_REPLICA ); // for timestamps
|
|
|
|
$ipbfields = [
|
|
];
|
|
$revfields = [
|
|
];
|
|
|
|
return [
|
|
'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
|
|
'rc_timestamp' => $db->timestamp(),
|
|
'rc_namespace' => 0,
|
|
'rc_title' => 'Test',
|
|
'rc_this_oldid' => 42,
|
|
'rc_last_oldid' => 41,
|
|
'rc_source' => 'test',
|
|
] ],
|
|
'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
|
|
'ipb_range_start' => '',
|
|
'ipb_range_end' => '',
|
|
'ipb_timestamp' => $db->timestamp(),
|
|
'ipb_expiry' => $db->getInfinity(),
|
|
] ],
|
|
'revision' => [ 'revision', 'rev_user', 'rev_id', [
|
|
'rev_page' => 42,
|
|
'rev_text_id' => 42,
|
|
'rev_len' => 0,
|
|
'rev_timestamp' => $db->timestamp(),
|
|
] ],
|
|
];
|
|
}
|
|
|
|
public static function provideStages() {
|
|
return [
|
|
'old' => [ SCHEMA_COMPAT_OLD ],
|
|
'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
|
|
'read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
|
|
'new' => [ SCHEMA_COMPAT_NEW ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
* @expectedException InvalidArgumentException
|
|
* @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
|
|
*/
|
|
public function testInsertWrong( $stage ) {
|
|
$m = $this->makeMigration( $stage );
|
|
$m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
* @expectedException InvalidArgumentException
|
|
* @expectedExceptionMessage Must use getInsertValues() for rc_user
|
|
*/
|
|
public function testInsertWithTempTableWrong( $stage ) {
|
|
$m = $this->makeMigration( $stage );
|
|
$m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
*/
|
|
public function testInsertWithTempTableDeprecated( $stage ) {
|
|
$wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
|
|
$wrap->formerTempTables += [ 'rc_user' => '1.30' ];
|
|
|
|
$this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
|
|
$m = $this->makeMigration( $stage );
|
|
list( $fields, $callback )
|
|
= $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
|
|
$this->assertTrue( is_callable( $callback ) );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideStages
|
|
* @param int $stage
|
|
* @expectedException InvalidArgumentException
|
|
* @expectedExceptionMessage $extra[rev_timestamp] is not provided
|
|
*/
|
|
public function testInsertWithTempTableCallbackMissingFields( $stage ) {
|
|
$m = $this->makeMigration( $stage );
|
|
list( $fields, $callback )
|
|
= $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
|
|
$callback( 1, [] );
|
|
}
|
|
|
|
public function testInsertUserIdentity() {
|
|
$this->setMwGlobals( [
|
|
// for User::getActorId()
|
|
'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
|
|
] );
|
|
$this->overrideMwServices();
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
$userIdentity = $this->getMock( UserIdentity::class );
|
|
$userIdentity->method( 'getId' )->willReturn( $user->getId() );
|
|
$userIdentity->method( 'getName' )->willReturn( $user->getName() );
|
|
$userIdentity->method( 'getActorId' )->willReturn( 0 );
|
|
|
|
list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
|
|
->insertWithTempTable( $this->db, 'rev_comment', '' );
|
|
$m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
|
|
list( $fields, $callback ) =
|
|
$m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
|
|
$extraFields = [
|
|
'rev_page' => 42,
|
|
'rev_text_id' => 42,
|
|
'rev_len' => 0,
|
|
'rev_timestamp' => $this->db->timestamp(),
|
|
] + $cFields;
|
|
$this->db->insert( 'revision', $extraFields + $fields, __METHOD__ );
|
|
$id = $this->db->insertId();
|
|
$callback( $id, $extraFields );
|
|
$cCallback( $id );
|
|
|
|
$qi = $m->getJoin( 'rev_user' );
|
|
$row = $this->db->selectRow(
|
|
[ 'revision' ] + $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
|
|
);
|
|
$this->assertSame( $user->getId(), (int)$row->rev_user );
|
|
$this->assertSame( $user->getName(), $row->rev_user_text );
|
|
$this->assertSame( $user->getActorId(), (int)$row->rev_actor );
|
|
|
|
$m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
|
|
$fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
|
|
$this->assertSame( $user->getId(), $fields['dummy_user'] );
|
|
$this->assertSame( $user->getName(), $fields['dummy_user_text'] );
|
|
$this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
|
|
}
|
|
|
|
public function testNewMigration() {
|
|
$m = ActorMigration::newMigration();
|
|
$this->assertInstanceOf( ActorMigration::class, $m );
|
|
$this->assertSame( $m, ActorMigration::newMigration() );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideIsAnon
|
|
* @param int $stage
|
|
* @param string $isAnon
|
|
* @param string $isNotAnon
|
|
*/
|
|
public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
|
|
$m = $this->makeMigration( $stage );
|
|
$this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
|
|
$this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
|
|
}
|
|
|
|
public static function provideIsAnon() {
|
|
return [
|
|
'old' => [ SCHEMA_COMPAT_OLD, 'foo = 0', 'foo != 0' ],
|
|
'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'foo = 0', 'foo != 0' ],
|
|
'read-new' => [
|
|
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'foo IS NULL', 'foo IS NOT NULL'
|
|
],
|
|
'new' => [ SCHEMA_COMPAT_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
|
|
];
|
|
}
|
|
|
|
}
|