2018-01-29 15:54:02 +00:00
|
|
|
<?php
|
2019-05-28 14:04:23 +00:00
|
|
|
|
2018-09-20 17:29:04 +00:00
|
|
|
namespace MediaWiki\Tests\Revision;
|
2018-01-29 15:54:02 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
use InvalidArgumentException;
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
|
|
|
use MediaWiki\Revision\SlotRecord;
|
2018-04-17 07:49:20 +00:00
|
|
|
use Revision;
|
|
|
|
|
use WikitextContent;
|
2018-01-29 15:54:02 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tests RevisionStore against the intermediate MCR DB schema for use during schema migration.
|
|
|
|
|
*
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore
|
2018-01-29 15:54:02 +00:00
|
|
|
*
|
|
|
|
|
* @group RevisionStore
|
|
|
|
|
* @group Storage
|
|
|
|
|
* @group Database
|
|
|
|
|
* @group medium
|
|
|
|
|
*/
|
|
|
|
|
class McrWriteBothRevisionStoreDbTest extends RevisionStoreDbTestBase {
|
|
|
|
|
|
|
|
|
|
use McrWriteBothSchemaOverride;
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
protected function revisionToRow( Revision $rev, $options = [ 'page', 'user', 'comment' ] ) {
|
|
|
|
|
$row = parent::revisionToRow( $rev, $options );
|
2018-01-29 15:54:02 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
$row->rev_text_id = (string)$rev->getTextId();
|
|
|
|
|
$row->rev_content_format = (string)$rev->getContentFormat();
|
|
|
|
|
$row->rev_content_model = (string)$rev->getContentModel();
|
|
|
|
|
|
|
|
|
|
return $row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
|
2018-06-28 18:32:03 +00:00
|
|
|
// New schema is being written
|
2018-01-29 15:54:02 +00:00
|
|
|
$this->assertSelect(
|
2018-04-17 07:49:20 +00:00
|
|
|
'slots',
|
|
|
|
|
[ 'count(*)' ],
|
|
|
|
|
[ 'slot_revision_id' => $rev->getId() ],
|
|
|
|
|
[ [ '1' ] ]
|
2018-01-29 15:54:02 +00:00
|
|
|
);
|
2018-04-17 07:49:20 +00:00
|
|
|
|
2018-01-29 15:54:02 +00:00
|
|
|
$this->assertSelect(
|
|
|
|
|
'content',
|
|
|
|
|
[ 'count(*)' ],
|
|
|
|
|
[ 'content_address' => $rev->getSlot( 'main' )->getAddress() ],
|
|
|
|
|
[ [ '1' ] ]
|
|
|
|
|
);
|
2018-04-17 07:49:20 +00:00
|
|
|
|
2018-06-28 18:32:03 +00:00
|
|
|
// Legacy schema is still being written
|
|
|
|
|
$this->assertSelect(
|
|
|
|
|
[ 'revision', 'text' ],
|
|
|
|
|
[ 'count(*)' ],
|
|
|
|
|
[ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
|
|
|
|
|
[ [ 1 ] ],
|
|
|
|
|
[],
|
2019-03-06 17:17:27 +00:00
|
|
|
[ 'text' => [ 'JOIN', [ 'rev_text_id = old_id' ] ] ]
|
2018-06-28 18:32:03 +00:00
|
|
|
);
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
parent::assertRevisionExistsInDatabase( $rev );
|
2018-01-29 15:54:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param SlotRecord $a
|
|
|
|
|
* @param SlotRecord $b
|
|
|
|
|
*/
|
|
|
|
|
protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) {
|
|
|
|
|
parent::assertSameSlotContent( $a, $b );
|
|
|
|
|
|
|
|
|
|
// Assert that the same content ID has been used
|
|
|
|
|
if ( $a->hasContentId() && $b->hasContentId() ) {
|
|
|
|
|
$this->assertSame( $a->getContentId(), $b->getContentId() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
public function provideInsertRevisionOn_failures() {
|
|
|
|
|
foreach ( parent::provideInsertRevisionOn_failures() as $case ) {
|
|
|
|
|
yield $case;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield 'slot that is not main slot' => [
|
|
|
|
|
[
|
|
|
|
|
'content' => [
|
|
|
|
|
'main' => new WikitextContent( 'Chicken' ),
|
|
|
|
|
'lalala' => new WikitextContent( 'Duck' ),
|
|
|
|
|
],
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
|
|
|
|
new InvalidArgumentException( 'Only the main slot is supported' )
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideNewMutableRevisionFromArray() {
|
|
|
|
|
foreach ( parent::provideNewMutableRevisionFromArray() as $case ) {
|
|
|
|
|
yield $case;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield 'Basic array, with page & id' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'text_id' => 2,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
|
|
|
|
'content_format' => 'text/x-wiki',
|
|
|
|
|
'content_model' => 'wikitext',
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRow
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionFromArchiveRow_unmigratedArchiveRow() {
|
|
|
|
|
// The main purpose of this test is to assert that after reading an archive
|
|
|
|
|
// row using the old schema it can be inserted into the revision table,
|
|
|
|
|
// and a slot row is created based on slot emulated from the old-style archive row,
|
|
|
|
|
// when none such slot row exists yet.
|
|
|
|
|
|
|
|
|
|
$title = $this->getTestPage()->getTitle();
|
|
|
|
|
|
|
|
|
|
$this->db->insert(
|
|
|
|
|
'text',
|
|
|
|
|
[ 'old_text' => 'Just a test', 'old_flags' => 'utf-8' ],
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$textId = $this->db->insertId();
|
|
|
|
|
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'ar_minor_edit' => '0',
|
|
|
|
|
'ar_user' => '0',
|
|
|
|
|
'ar_user_text' => '127.0.0.1',
|
|
|
|
|
'ar_actor' => null,
|
|
|
|
|
'ar_len' => '11',
|
|
|
|
|
'ar_deleted' => '0',
|
|
|
|
|
'ar_rev_id' => 112277,
|
|
|
|
|
'ar_timestamp' => $this->db->timestamp( '20180101000000' ),
|
|
|
|
|
'ar_sha1' => 'deadbeef',
|
|
|
|
|
'ar_page_id' => $title->getArticleID(),
|
|
|
|
|
'ar_comment_text' => 'just a test',
|
|
|
|
|
'ar_comment_data' => null,
|
|
|
|
|
'ar_comment_cid' => null,
|
|
|
|
|
'ar_content_format' => null,
|
|
|
|
|
'ar_content_model' => null,
|
|
|
|
|
'ts_tags' => null,
|
|
|
|
|
'ar_id' => 17,
|
|
|
|
|
'ar_namespace' => $title->getNamespace(),
|
|
|
|
|
'ar_title' => $title->getDBkey(),
|
|
|
|
|
'ar_text_id' => $textId,
|
|
|
|
|
'ar_parent_id' => 112211,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$rev = $store->newRevisionFromArchiveRow( $row );
|
|
|
|
|
|
|
|
|
|
// re-insert archived revision
|
|
|
|
|
$return = $store->insertRevisionOn( $rev, $this->db );
|
|
|
|
|
|
|
|
|
|
// is the new revision correct?
|
|
|
|
|
$this->assertRevisionCompleteness( $return );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $rev, $return );
|
|
|
|
|
|
|
|
|
|
// can we load it from the store?
|
|
|
|
|
$loaded = $store->getRevisionById( $return->getId() );
|
|
|
|
|
$this->assertNotNull( $loaded );
|
|
|
|
|
$this->assertRevisionCompleteness( $loaded );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $return, $loaded );
|
|
|
|
|
|
|
|
|
|
// can we find it directly in the database?
|
|
|
|
|
$this->assertRevisionExistsInDatabase( $return );
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 08:48:10 +00:00
|
|
|
/**
|
|
|
|
|
* Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
|
|
|
|
|
* revision.
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
protected function getSlotRevisionConditions( $revId ) {
|
|
|
|
|
return [ 'rev_id' => $revId ];
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-29 15:54:02 +00:00
|
|
|
}
|