During development a lot of classes were placed in MediaWiki\Storage\. The precedent set would mean that every class relating to something stored in a database table, plus all related value classes and such, would go into that namespace. Let's put them into MediaWiki\Revision\ instead. Then future classes related to the 'page' table can go into MediaWiki\Page\, future classes related to the 'user' table can go into MediaWiki\User\, and so on. Note I didn't move DerivedPageDataUpdater, PageUpdateException, PageUpdater, or RevisionSlotsUpdate in this patch. If these are kept long-term, they probably belong in MediaWiki\Page\ or MediaWiki\Edit\ instead. Bug: T204158 Change-Id: I16bea8927566a3c73c07e4f4afb3537e05aa04a5
566 lines
17 KiB
PHP
566 lines
17 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\Revision;
|
|
|
|
use CommentStore;
|
|
use HashBagOStuff;
|
|
use InvalidArgumentException;
|
|
use Language;
|
|
use MediaWiki\MediaWikiServices;
|
|
use MediaWiki\Revision\RevisionAccessException;
|
|
use MediaWiki\Revision\RevisionStore;
|
|
use MediaWiki\Revision\SlotRecord;
|
|
use MediaWiki\Storage\SqlBlobStore;
|
|
use MediaWikiTestCase;
|
|
use MWException;
|
|
use Title;
|
|
use WANObjectCache;
|
|
use Wikimedia\Rdbms\Database;
|
|
use Wikimedia\Rdbms\LoadBalancer;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
use WikitextContent;
|
|
|
|
class RevisionStoreTest extends MediaWikiTestCase {
|
|
|
|
private function useTextId() {
|
|
global $wgMultiContentRevisionSchemaMigrationStage;
|
|
|
|
return (bool)( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD );
|
|
}
|
|
|
|
/**
|
|
* @param LoadBalancer $loadBalancer
|
|
* @param SqlBlobStore $blobStore
|
|
* @param WANObjectCache $WANObjectCache
|
|
*
|
|
* @return RevisionStore
|
|
*/
|
|
private function getRevisionStore(
|
|
$loadBalancer = null,
|
|
$blobStore = null,
|
|
$WANObjectCache = null
|
|
) {
|
|
global $wgMultiContentRevisionSchemaMigrationStage;
|
|
// the migration stage should be irrelevant, since all the tests that interact with
|
|
// the database are in RevisionStoreDbTest, not here.
|
|
|
|
return new RevisionStore(
|
|
$loadBalancer ?: $this->getMockLoadBalancer(),
|
|
$blobStore ?: $this->getMockSqlBlobStore(),
|
|
$WANObjectCache ?: $this->getHashWANObjectCache(),
|
|
MediaWikiServices::getInstance()->getCommentStore(),
|
|
MediaWikiServices::getInstance()->getContentModelStore(),
|
|
MediaWikiServices::getInstance()->getSlotRoleStore(),
|
|
$wgMultiContentRevisionSchemaMigrationStage,
|
|
MediaWikiServices::getInstance()->getActorMigration()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
|
|
*/
|
|
private function getMockLoadBalancer() {
|
|
return $this->getMockBuilder( LoadBalancer::class )
|
|
->disableOriginalConstructor()->getMock();
|
|
}
|
|
|
|
/**
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|Database
|
|
*/
|
|
private function getMockDatabase() {
|
|
return $this->getMockBuilder( Database::class )
|
|
->disableOriginalConstructor()->getMock();
|
|
}
|
|
|
|
/**
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
|
|
*/
|
|
private function getMockSqlBlobStore() {
|
|
return $this->getMockBuilder( SqlBlobStore::class )
|
|
->disableOriginalConstructor()->getMock();
|
|
}
|
|
|
|
/**
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
|
|
*/
|
|
private function getMockCommentStore() {
|
|
return $this->getMockBuilder( CommentStore::class )
|
|
->disableOriginalConstructor()->getMock();
|
|
}
|
|
|
|
private function getHashWANObjectCache() {
|
|
return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
|
|
}
|
|
|
|
public function provideSetContentHandlerUseDB() {
|
|
return [
|
|
// ContentHandlerUseDB can be true of false pre migration.
|
|
[ false, SCHEMA_COMPAT_OLD, false ],
|
|
[ true, SCHEMA_COMPAT_OLD, false ],
|
|
// During and after migration it can not be false...
|
|
[ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, true ],
|
|
[ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, true ],
|
|
[ false, SCHEMA_COMPAT_NEW, true ],
|
|
// ...but it can be true.
|
|
[ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
|
|
[ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
|
|
[ true, SCHEMA_COMPAT_NEW, false ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideSetContentHandlerUseDB
|
|
* @covers \MediaWiki\Revision\RevisionStore::getContentHandlerUseDB
|
|
* @covers \MediaWiki\Revision\RevisionStore::setContentHandlerUseDB
|
|
*/
|
|
public function testSetContentHandlerUseDB( $contentHandlerDb, $migrationMode, $expectedFail ) {
|
|
if ( $expectedFail ) {
|
|
$this->setExpectedException( MWException::class );
|
|
}
|
|
|
|
$nameTables = MediaWikiServices::getInstance()->getNameTableStoreFactory();
|
|
|
|
$store = new RevisionStore(
|
|
$this->getMockLoadBalancer(),
|
|
$this->getMockSqlBlobStore(),
|
|
$this->getHashWANObjectCache(),
|
|
$this->getMockCommentStore(),
|
|
$nameTables->getContentModels(),
|
|
$nameTables->getSlotRoles(),
|
|
$migrationMode,
|
|
MediaWikiServices::getInstance()->getActorMigration()
|
|
);
|
|
|
|
$store->setContentHandlerUseDB( $contentHandlerDb );
|
|
$this->assertSame( $contentHandlerDb, $store->getContentHandlerUseDB() );
|
|
}
|
|
|
|
public function testGetTitle_successFromPageId() {
|
|
$mockLoadBalancer = $this->getMockLoadBalancer();
|
|
// Title calls wfGetDB() so we have to set the main service
|
|
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
|
|
|
|
$db = $this->getMockDatabase();
|
|
// Title calls wfGetDB() which uses a regular Connection
|
|
$mockLoadBalancer->expects( $this->atLeastOnce() )
|
|
->method( 'getConnection' )
|
|
->willReturn( $db );
|
|
|
|
// First call to Title::newFromID, faking no result (db lag?)
|
|
$db->expects( $this->at( 0 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( (object)[
|
|
'page_namespace' => '1',
|
|
'page_title' => 'Food',
|
|
] );
|
|
|
|
$store = $this->getRevisionStore( $mockLoadBalancer );
|
|
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
|
|
|
|
$this->assertSame( 1, $title->getNamespace() );
|
|
$this->assertSame( 'Food', $title->getDBkey() );
|
|
}
|
|
|
|
public function testGetTitle_successFromPageIdOnFallback() {
|
|
$mockLoadBalancer = $this->getMockLoadBalancer();
|
|
// Title calls wfGetDB() so we have to set the main service
|
|
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
|
|
|
|
$db = $this->getMockDatabase();
|
|
// Title calls wfGetDB() which uses a regular Connection
|
|
// Assert that the first call uses a REPLICA and the second falls back to master
|
|
$mockLoadBalancer->expects( $this->exactly( 2 ) )
|
|
->method( 'getConnection' )
|
|
->willReturn( $db );
|
|
// RevisionStore getTitle uses a ConnectionRef
|
|
$mockLoadBalancer->expects( $this->atLeastOnce() )
|
|
->method( 'getConnectionRef' )
|
|
->willReturn( $db );
|
|
|
|
// First call to Title::newFromID, faking no result (db lag?)
|
|
$db->expects( $this->at( 0 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// First select using rev_id, faking no result (db lag?)
|
|
$db->expects( $this->at( 1 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
[ 'revision', 'page' ],
|
|
$this->anything(),
|
|
[ 'rev_id' => 2 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// Second call to Title::newFromID, no result
|
|
$db->expects( $this->at( 2 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( (object)[
|
|
'page_namespace' => '2',
|
|
'page_title' => 'Foodey',
|
|
] );
|
|
|
|
$store = $this->getRevisionStore( $mockLoadBalancer );
|
|
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
|
|
|
|
$this->assertSame( 2, $title->getNamespace() );
|
|
$this->assertSame( 'Foodey', $title->getDBkey() );
|
|
}
|
|
|
|
public function testGetTitle_successFromRevId() {
|
|
$mockLoadBalancer = $this->getMockLoadBalancer();
|
|
// Title calls wfGetDB() so we have to set the main service
|
|
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
|
|
|
|
$db = $this->getMockDatabase();
|
|
// Title calls wfGetDB() which uses a regular Connection
|
|
$mockLoadBalancer->expects( $this->atLeastOnce() )
|
|
->method( 'getConnection' )
|
|
->willReturn( $db );
|
|
// RevisionStore getTitle uses a ConnectionRef
|
|
$mockLoadBalancer->expects( $this->atLeastOnce() )
|
|
->method( 'getConnectionRef' )
|
|
->willReturn( $db );
|
|
|
|
// First call to Title::newFromID, faking no result (db lag?)
|
|
$db->expects( $this->at( 0 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// First select using rev_id, faking no result (db lag?)
|
|
$db->expects( $this->at( 1 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
[ 'revision', 'page' ],
|
|
$this->anything(),
|
|
[ 'rev_id' => 2 ]
|
|
)
|
|
->willReturn( (object)[
|
|
'page_namespace' => '1',
|
|
'page_title' => 'Food2',
|
|
] );
|
|
|
|
$store = $this->getRevisionStore( $mockLoadBalancer );
|
|
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
|
|
|
|
$this->assertSame( 1, $title->getNamespace() );
|
|
$this->assertSame( 'Food2', $title->getDBkey() );
|
|
}
|
|
|
|
public function testGetTitle_successFromRevIdOnFallback() {
|
|
$mockLoadBalancer = $this->getMockLoadBalancer();
|
|
// Title calls wfGetDB() so we have to set the main service
|
|
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
|
|
|
|
$db = $this->getMockDatabase();
|
|
// Title calls wfGetDB() which uses a regular Connection
|
|
// Assert that the first call uses a REPLICA and the second falls back to master
|
|
$mockLoadBalancer->expects( $this->exactly( 2 ) )
|
|
->method( 'getConnection' )
|
|
->willReturn( $db );
|
|
// RevisionStore getTitle uses a ConnectionRef
|
|
$mockLoadBalancer->expects( $this->atLeastOnce() )
|
|
->method( 'getConnectionRef' )
|
|
->willReturn( $db );
|
|
|
|
// First call to Title::newFromID, faking no result (db lag?)
|
|
$db->expects( $this->at( 0 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// First select using rev_id, faking no result (db lag?)
|
|
$db->expects( $this->at( 1 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
[ 'revision', 'page' ],
|
|
$this->anything(),
|
|
[ 'rev_id' => 2 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// Second call to Title::newFromID, no result
|
|
$db->expects( $this->at( 2 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( false );
|
|
|
|
// Second select using rev_id, result
|
|
$db->expects( $this->at( 3 ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
[ 'revision', 'page' ],
|
|
$this->anything(),
|
|
[ 'rev_id' => 2 ]
|
|
)
|
|
->willReturn( (object)[
|
|
'page_namespace' => '2',
|
|
'page_title' => 'Foodey',
|
|
] );
|
|
|
|
$store = $this->getRevisionStore( $mockLoadBalancer );
|
|
$title = $store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
|
|
|
|
$this->assertSame( 2, $title->getNamespace() );
|
|
$this->assertSame( 'Foodey', $title->getDBkey() );
|
|
}
|
|
|
|
/**
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTitle
|
|
*/
|
|
public function testGetTitle_correctFallbackAndthrowsExceptionAfterFallbacks() {
|
|
$mockLoadBalancer = $this->getMockLoadBalancer();
|
|
// Title calls wfGetDB() so we have to set the main service
|
|
$this->setService( 'DBLoadBalancer', $mockLoadBalancer );
|
|
|
|
$db = $this->getMockDatabase();
|
|
// Title calls wfGetDB() which uses a regular Connection
|
|
// Assert that the first call uses a REPLICA and the second falls back to master
|
|
|
|
// RevisionStore getTitle uses getConnectionRef
|
|
// Title::newFromID uses getConnection
|
|
foreach ( [ 'getConnection', 'getConnectionRef' ] as $method ) {
|
|
$mockLoadBalancer->expects( $this->exactly( 2 ) )
|
|
->method( $method )
|
|
->willReturnCallback( function ( $masterOrReplica ) use ( $db ) {
|
|
static $callCounter = 0;
|
|
$callCounter++;
|
|
// The first call should be to a REPLICA, and the second a MASTER.
|
|
if ( $callCounter === 1 ) {
|
|
$this->assertSame( DB_REPLICA, $masterOrReplica );
|
|
} elseif ( $callCounter === 2 ) {
|
|
$this->assertSame( DB_MASTER, $masterOrReplica );
|
|
}
|
|
return $db;
|
|
} );
|
|
}
|
|
// First and third call to Title::newFromID, faking no result
|
|
foreach ( [ 0, 2 ] as $counter ) {
|
|
$db->expects( $this->at( $counter ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
'page',
|
|
$this->anything(),
|
|
[ 'page_id' => 1 ]
|
|
)
|
|
->willReturn( false );
|
|
}
|
|
|
|
foreach ( [ 1, 3 ] as $counter ) {
|
|
$db->expects( $this->at( $counter ) )
|
|
->method( 'selectRow' )
|
|
->with(
|
|
[ 'revision', 'page' ],
|
|
$this->anything(),
|
|
[ 'rev_id' => 2 ]
|
|
)
|
|
->willReturn( false );
|
|
}
|
|
|
|
$store = $this->getRevisionStore( $mockLoadBalancer );
|
|
|
|
$this->setExpectedException( RevisionAccessException::class );
|
|
$store->getTitle( 1, 2, RevisionStore::READ_NORMAL );
|
|
}
|
|
|
|
public function provideNewRevisionFromRow_legacyEncoding_applied() {
|
|
yield 'windows-1252, old_flags is empty' => [
|
|
'windows-1252',
|
|
'en',
|
|
[
|
|
'old_flags' => '',
|
|
'old_text' => "S\xF6me Content",
|
|
],
|
|
'Söme Content'
|
|
];
|
|
|
|
yield 'windows-1252, old_flags is null' => [
|
|
'windows-1252',
|
|
'en',
|
|
[
|
|
'old_flags' => null,
|
|
'old_text' => "S\xF6me Content",
|
|
],
|
|
'Söme Content'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideNewRevisionFromRow_legacyEncoding_applied
|
|
*
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
*/
|
|
public function testNewRevisionFromRow_legacyEncoding_applied( $encoding, $locale, $row, $text ) {
|
|
if ( !$this->useTextId() ) {
|
|
$this->markTestSkipped( 'No longer applicable with MCR schema' );
|
|
}
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
|
|
|
$blobStore = new SqlBlobStore( $lb, $cache );
|
|
$blobStore->setLegacyEncoding( $encoding, Language::factory( $locale ) );
|
|
|
|
$store = $this->getRevisionStore( $lb, $blobStore, $cache );
|
|
|
|
$record = $store->newRevisionFromRow(
|
|
$this->makeRow( $row ),
|
|
0,
|
|
Title::newFromText( __METHOD__ . '-UTPage' )
|
|
);
|
|
|
|
$this->assertSame( $text, $record->getContent( SlotRecord::MAIN )->serialize() );
|
|
}
|
|
|
|
/**
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
*/
|
|
public function testNewRevisionFromRow_legacyEncoding_ignored() {
|
|
if ( !$this->useTextId() ) {
|
|
$this->markTestSkipped( 'No longer applicable with MCR schema' );
|
|
}
|
|
|
|
$row = [
|
|
'old_flags' => 'utf-8',
|
|
'old_text' => 'Söme Content',
|
|
];
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
|
|
|
$blobStore = new SqlBlobStore( $lb, $cache );
|
|
$blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
|
|
|
|
$store = $this->getRevisionStore( $lb, $blobStore, $cache );
|
|
|
|
$record = $store->newRevisionFromRow(
|
|
$this->makeRow( $row ),
|
|
0,
|
|
Title::newFromText( __METHOD__ . '-UTPage' )
|
|
);
|
|
$this->assertSame( 'Söme Content', $record->getContent( SlotRecord::MAIN )->serialize() );
|
|
}
|
|
|
|
private function makeRow( array $array ) {
|
|
$row = $array + [
|
|
'rev_id' => 7,
|
|
'rev_page' => 5,
|
|
'rev_timestamp' => '20110101000000',
|
|
'rev_user_text' => 'Tester',
|
|
'rev_user' => 17,
|
|
'rev_minor_edit' => 0,
|
|
'rev_deleted' => 0,
|
|
'rev_len' => 100,
|
|
'rev_parent_id' => 0,
|
|
'rev_sha1' => 'deadbeef',
|
|
'rev_comment_text' => 'Testing',
|
|
'rev_comment_data' => '{}',
|
|
'rev_comment_cid' => 111,
|
|
'page_namespace' => 0,
|
|
'page_title' => 'TEST',
|
|
'page_id' => 5,
|
|
'page_latest' => 7,
|
|
'page_is_redirect' => 0,
|
|
'page_len' => 100,
|
|
'user_name' => 'Tester',
|
|
];
|
|
|
|
if ( $this->useTextId() ) {
|
|
$row += [
|
|
'rev_content_format' => CONTENT_FORMAT_TEXT,
|
|
'rev_content_model' => CONTENT_MODEL_TEXT,
|
|
'rev_text_id' => 11,
|
|
'old_id' => 11,
|
|
'old_text' => 'Hello World',
|
|
'old_flags' => 'utf-8',
|
|
];
|
|
} else {
|
|
if ( !isset( $row['content'] ) && isset( $array['old_text'] ) ) {
|
|
$row['content'] = [
|
|
'main' => new WikitextContent( $array['old_text'] ),
|
|
];
|
|
}
|
|
}
|
|
|
|
return (object)$row;
|
|
}
|
|
|
|
public function provideMigrationConstruction() {
|
|
return [
|
|
[ SCHEMA_COMPAT_OLD, false ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
|
|
[ SCHEMA_COMPAT_NEW, false ],
|
|
[ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, true ],
|
|
[ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, true ],
|
|
[ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, true ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @covers \MediaWiki\Revision\RevisionStore::__construct
|
|
* @dataProvider provideMigrationConstruction
|
|
*/
|
|
public function testMigrationConstruction( $migration, $expectException ) {
|
|
if ( $expectException ) {
|
|
$this->setExpectedException( InvalidArgumentException::class );
|
|
}
|
|
$loadBalancer = $this->getMockLoadBalancer();
|
|
$blobStore = $this->getMockSqlBlobStore();
|
|
$cache = $this->getHashWANObjectCache();
|
|
$commentStore = $this->getMockCommentStore();
|
|
$services = MediaWikiServices::getInstance();
|
|
$nameTables = $services->getNameTableStoreFactory();
|
|
$contentModelStore = $nameTables->getContentModels();
|
|
$slotRoleStore = $nameTables->getSlotRoles();
|
|
$store = new RevisionStore(
|
|
$loadBalancer,
|
|
$blobStore,
|
|
$cache,
|
|
$commentStore,
|
|
$nameTables->getContentModels(),
|
|
$nameTables->getSlotRoles(),
|
|
$migration,
|
|
$services->getActorMigration()
|
|
);
|
|
if ( !$expectException ) {
|
|
$store = TestingAccessWrapper::newFromObject( $store );
|
|
$this->assertSame( $loadBalancer, $store->loadBalancer );
|
|
$this->assertSame( $blobStore, $store->blobStore );
|
|
$this->assertSame( $cache, $store->cache );
|
|
$this->assertSame( $commentStore, $store->commentStore );
|
|
$this->assertSame( $contentModelStore, $store->contentModelStore );
|
|
$this->assertSame( $slotRoleStore, $store->slotRoleStore );
|
|
$this->assertSame( $migration, $store->mcrMigrationStage );
|
|
}
|
|
}
|
|
|
|
}
|