if ( $current ) {
$fields = [
- 'page' => $title->getArticleID(),
- 'user_text' => $user->getName(),
- 'user' => $user->getId(),
- 'actor' => $user->getActorId(),
- 'comment' => $comment,
- 'minor_edit' => $minor,
- 'text_id' => $current->rev_text_id,
- 'parent_id' => $current->page_latest,
- 'len' => $current->rev_len,
- 'sha1' => $current->rev_sha1
+ 'page' => $title->getArticleID(),
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
+ 'actor' => $user->getActorId(),
+ 'comment' => $comment,
+ 'minor_edit' => $minor,
+ 'text_id' => $current->rev_text_id,
+ 'parent_id' => $current->page_latest,
+ 'slot_origin' => $current->page_latest,
+ 'len' => $current->rev_len,
+ 'sha1' => $current->rev_sha1
];
if ( $this->contentHandlerUseDB ) {
$mainSlotRow->content_address = 'tt:' . $row->rev_text_id;
}
+ // This is used by null-revisions
+ $mainSlotRow->slot_origin = isset( $row->slot_origin )
+ ? intval( $row->slot_origin )
+ : null;
+
if ( isset( $row->old_text ) ) {
// this happens when the text-table gets joined directly, in the pre-1.30 schema
$blobData = isset( $row->old_text ) ? strval( $row->old_text ) : null;
$mainSlotRow->slot_content_id = isset( $row['text_id'] )
? intval( $row['text_id'] )
: null;
+ $mainSlotRow->slot_origin = isset( $row['slot_origin'] )
+ ? intval( $row['slot_origin'] )
+ : null;
$mainSlotRow->content_address = isset( $row['text_id'] )
? 'tt:' . intval( $row['text_id'] )
: null;
throw new MWException( 'Revision constructor passed invalid row format.' );
}
- // With the old schema, the content changes with every revision.
- // ...except for null-revisions. Would be nice if we could detect them.
- $mainSlotRow->slot_inherited = 0;
+ // With the old schema, the content changes with every revision,
+ // except for null-revisions.
+ if ( !isset( $mainSlotRow->slot_origin ) ) {
+ $mainSlotRow->slot_origin = $mainSlotRow->slot_revision_id;
+ }
if ( $mainSlotRow->model_name === null ) {
$mainSlotRow->model_name = function ( SlotRecord $slot ) use ( $title ) {
* @return SlotRecord
*/
public static function newInherited( SlotRecord $slot ) {
+ // Sanity check - we can't inherit from a Slot that's not attached to a revision.
+ $slot->getRevision();
+ $slot->getOrigin();
+ $slot->getAddress();
+
+ // NOTE: slot_origin and content_address are copied from $slot.
return self::newDerived( $slot, [
- 'slot_inherited' => true,
'slot_revision_id' => null,
] );
}
$row = [
'slot_id' => null, // not yet known
'slot_revision_id' => null, // not yet known
- 'slot_inherited' => 0, // not inherited
+ 'slot_origin' => null, // not yet known, will be set in newSaved()
'content_size' => null, // compute later
'content_sha1' => null, // compute later
'slot_content_id' => null, // not yet known, will be set in newSaved()
);
}
- if ( $protoSlot->isInherited() && !$protoSlot->hasAddress() ) {
- throw new InvalidArgumentException(
- "An inherited blob should have a content address!"
- );
+ if ( $protoSlot->isInherited() ) {
+ if ( !$protoSlot->hasAddress() ) {
+ throw new InvalidArgumentException(
+ "An inherited blob should have a content address!"
+ );
+ }
+ if ( !$protoSlot->hasField( 'slot_origin' ) ) {
+ throw new InvalidArgumentException(
+ "A saved inherited slot should have an origin set!"
+ );
+ }
+ $origin = $protoSlot->getOrigin();
+ } else {
+ $origin = $revisionId;
}
return self::newDerived( $protoSlot, [
'slot_revision_id' => $revisionId,
'slot_content_id' => $contentId,
+ 'slot_origin' => $origin,
'content_address' => $contentAddress,
] );
}
'$row->slot_revision_id',
'must exist'
);
- Assert::parameter(
- property_exists( $row, 'slot_inherited' ),
- '$row->slot_inherited',
- 'must exist'
- );
Assert::parameter(
property_exists( $row, 'slot_content_id' ),
'$row->slot_content_id',
'$row->model_name',
'must exist'
);
+ Assert::parameter(
+ property_exists( $row, 'slot_origin' ),
+ '$row->slot_origin',
+ 'must exist'
+ );
+ Assert::parameter(
+ !property_exists( $row, 'slot_inherited' ),
+ '$row->slot_inherited',
+ 'must not exist'
+ );
+ Assert::parameter(
+ !property_exists( $row, 'slot_revision' ),
+ '$row->slot_revision',
+ 'must not exist'
+ );
$this->row = $row;
$this->content = $content;
return $this->getIntField( 'slot_revision_id' );
}
+ /**
+ * Returns the revision ID of the revision that originated the slot's content.
+ *
+ * @return int
+ */
+ public function getOrigin() {
+ return $this->getIntField( 'slot_origin' );
+ }
+
/**
* Whether this slot was inherited from an older revision.
*
+ * If this SlotRecord is already attached to a revision, this returns true
+ * if the slot's revision of origin is the same as the revision it belongs to.
+ *
+ * If this SlotRecord is not yet attached to a revision, this returns true
+ * if the slot already has an address.
+ *
* @return bool
*/
public function isInherited() {
- return $this->getIntField( 'slot_inherited' ) !== 0;
+ if ( $this->hasRevision() ) {
+ return $this->getRevision() !== $this->getOrigin();
+ } else {
+ return $this->hasAddress();
+ }
}
/**
// 1.31
[ 'addTable', 'slots', 'patch-slots.sql' ],
+ [ 'addField', 'slots', 'slot_origin', 'patch-slot-origin.sql' ],
[ 'addTable', 'content', 'patch-content.sql' ],
[ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
[ 'addTable', 'content_models', 'patch-content_models.sql' ],
// 1.31
[ 'addTable', 'slots', 'patch-slots.sql' ],
+ [ 'addField', 'slots', 'slot_origin', 'patch-slot-origin.sql' ],
[ 'addTable', 'content', 'patch-content.sql' ],
[ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
[ 'addTable', 'content_models', 'patch-content_models.sql' ],
// 1.31
[ 'addTable', 'slots', 'patch-slots.sql' ],
+ [ 'addField', 'slots', 'slot_origin', 'patch-slot-origin.sql' ],
[ 'addTable', 'content', 'patch-content.sql' ],
[ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
[ 'addTable', 'content_models', 'patch-content_models.sql' ],
// 1.31
[ 'addTable', 'slots', 'patch-slots-table.sql' ],
+ [ 'dropPgIndex', 'slots', 'slot_role_inherited' ],
+ [ 'dropPgField', 'slots', 'slot_inherited' ],
+ [ 'addPgField', 'slots', 'slot_origin', 'INTEGER NOT NULL' ],
+ [
+ 'addPgIndex',
+ 'slots',
+ 'slot_revision_origin_role',
+ '( slot_revision_id, slot_origin, slot_role_id )',
+ ],
[ 'addTable', 'content', 'patch-content-table.sql' ],
[ 'addTable', 'content_models', 'patch-content_models-table.sql' ],
[ 'addTable', 'slot_roles', 'patch-slot_roles-table.sql' ],
$this->db->query( "ALTER INDEX $old RENAME TO $new" );
}
+ protected function dropPgField( $table, $field ) {
+ $fi = $this->db->fieldInfo( $table, $field );
+ if ( is_null( $fi ) ) {
+ $this->output( "...$table table does not contain $field field.\n" );
+
+ return;
+ } else {
+ $this->output( "Dropping column '$table.$field'\n" );
+ $this->db->query( "ALTER TABLE $table DROP COLUMN $field" );
+ }
+ }
+
protected function addPgField( $table, $field, $type ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( !is_null( $fi ) ) {
[ 'addTable', 'content', 'patch-content.sql' ],
[ 'addTable', 'content_models', 'patch-content_models.sql' ],
[ 'addTable', 'slots', 'patch-slots.sql' ],
+ [ 'addField', 'slots', 'slot_origin', 'patch-slot-origin.sql' ],
[ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
[ 'migrateArchiveText' ],
[ 'addTable', 'actor', 'patch-actor-table.sql' ],
--- /dev/null
+--
+-- Replace slot_inherited with slot_origin.
+--
+-- NOTE: There is no release that has slot_inherited. This is only needed to transition between
+-- snapshot versions of 1.30.
+--
+-- NOTE: No code that writes to the slots table was merged yet, the table is assumed to be empty.
+--
+DROP INDEX /*i*/slot_role_inherited ON /*_*/slots;
+
+ALTER TABLE /*_*/slots
+ DROP COLUMN slot_inherited,
+ ADD COLUMN slot_origin bigint unsigned NOT NULL;
+
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
-- reference to content_id
slot_content_id bigint unsigned NOT NULL,
- -- whether the content is inherited (1) or new in this revision (0)
- slot_inherited tinyint unsigned NOT NULL DEFAULT 0,
+ -- The revision ID of the revision that originated the slot's content.
+ -- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ slot_origin bigint unsigned NOT NULL,
PRIMARY KEY ( slot_revision_id, slot_role_id )
) /*$wgDBTableOptions*/;
-- Index for finding revisions that modified a specific slot
-CREATE INDEX /*i*/slot_role_inherited ON /*_*/slots (slot_revision_id, slot_role_id, slot_inherited);
\ No newline at end of file
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
--- /dev/null
+--
+-- Replace slot_inherited with slot_origin.
+--
+-- NOTE: There is no release that has slot_inherited. This is only needed to transition between
+-- snapshot versions of 1.30.
+--
+-- NOTE: No code that writes to the slots table was merge yet, the table is assumed to be empty.
+--
+DROP INDEX /*i*/slot_role_inherited ON /*_*/slots;
+
+ALTER TABLE /*_*/slots DROP CONSTRAINT DF_slot_inherited, COLUMN slot_inherited;
+ALTER TABLE /*_*/slots ADD COLUMN slot_origin bigint NOT NULL;
+
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
-- reference to content_id
slot_content_id bigint unsigned NOT NULL CONSTRAINT FK_slots_content_id FOREIGN KEY REFERENCES content(content_id),
- -- whether the content is inherited (1) or new in this revision (0)
- slot_inherited tinyint unsigned NOT NULL CONSTRAINT DF_slot_inherited DEFAULT 0,
+ -- The revision ID of the revision that originated the slot's content.
+ -- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ slot_origin bigint unsigned NOT NULL,
CONSTRAINT PK_slots PRIMARY KEY (slot_revision_id, slot_role_id)
);
-- Index for finding revisions that modified a specific slot
-CREATE INDEX /*i*/slot_role_inherited ON /*_*/slots (slot_revision_id, slot_role_id, slot_inherited);
\ No newline at end of file
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
-- reference to content_id
slot_content_id bigint unsigned NOT NULL CONSTRAINT FK_slots_content_id FOREIGN KEY REFERENCES content(content_id),
- -- whether the content is inherited (1) or new in this revision (0)
- slot_inherited tinyint unsigned NOT NULL CONSTRAINT DF_slot_inherited DEFAULT 0,
+ -- The revision ID of the revision that originated the slot's content.
+ -- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ slot_origin bigint NOT NULL,
CONSTRAINT PK_slots PRIMARY KEY (slot_revision_id, slot_role_id)
);
-- Index for finding revisions that modified a specific slot
-CREATE INDEX /*i*/slot_role_inherited ON /*_*/slots (slot_revision_id, slot_role_id, slot_inherited);
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
--
-- The content table represents content objects. It's primary purpose is to provide the necessary
--- /dev/null
+--
+-- Replace slot_inherited with slot_origin.
+--
+-- NOTE: There is no release that has slot_inherited. This is only needed to transition between
+-- snapshot versions of 1.30.
+--
+-- NOTE: No code that writes to the slots table was merge yet, the table is assumed to be empty.
+--
+DROP INDEX &mw_prefix.slot_role_inherited;
+
+ALTER TABLE &mw_prefix.slots DROP COLUMN slot_inherited;
+ALTER TABLE &mw_prefix.slots ADD ( slot_origin NUMBER NOT NULL );
+
+CREATE INDEX &mw_prefix.slot_revision_origin_role ON &mw_prefix.slots (slot_revision_id, slot_origin, slot_role_id);
slot_revision_id NUMBER NOT NULL,
slot_role_id NUMBER NOT NULL,
slot_content_id NUMBER NOT NULL,
- slot_inherited CHAR(1) DEFAULT '0' NOT NULL
+ slot_origin NUMBER NOT NULL
);
ALTER TABLE &mw_prefix.slots ADD CONSTRAINT &mw_prefix.slots_pk PRIMARY KEY (slot_revision_id, slot_role_id);
-CREATE INDEX &mw_prefix.slot_role_inherited ON &mw_prefix.slots (slot_revision_id, slot_role_id, slot_inherited);
\ No newline at end of file
+CREATE INDEX &mw_prefix.slot_revision_origin_role ON &mw_prefix.slots (slot_revision_id, slot_origin, slot_role_id);
slot_revision_id NUMBER NOT NULL,
slot_role_id NUMBER NOT NULL,
slot_content_id NUMBER NOT NULL,
- slot_inherited CHAR(1) DEFAULT '0' NOT NULL
+ slot_origin NUMBER NOT NULL
);
ALTER TABLE &mw_prefix.slots ADD CONSTRAINT &mw_prefix.slots_pk PRIMARY KEY (slot_revision_id, slot_role_id);
-CREATE INDEX &mw_prefix.slot_role_inherited ON &mw_prefix.slots (slot_revision_id, slot_role_id, slot_inherited);
+CREATE INDEX &mw_prefix.slot_revision_origin_role ON &mw_prefix.slots (slot_revision_id, slot_origin, slot_role_id);
CREATE SEQUENCE content_content_id_seq;
slot_revision_id INTEGER NOT NULL,
slot_role_id SMALLINT NOT NULL,
slot_content_id INTEGER NOT NULL,
- slot_inherited SMALLINT NOT NULL DEFAULT 0,
+ slot_origin INTEGER NOT NULL,
PRIMARY KEY (slot_revision_id, slot_role_id)
);
-CREATE INDEX slot_role_inherited ON slots (slot_revision_id, slot_role_id, slot_inherited);
\ No newline at end of file
+CREATE INDEX slot_revision_origin_role ON slots (slot_revision_id, slot_origin, slot_role_id);
slot_revision_id INTEGER NOT NULL,
slot_role_id SMALLINT NOT NULL,
slot_content_id INTEGER NOT NULL,
- slot_inherited SMALLINT NOT NULL DEFAULT 0,
+ slot_origin INTEGER NOT NULL,
PRIMARY KEY (slot_revision_id, slot_role_id)
);
-CREATE INDEX slot_role_inherited ON slots (slot_revision_id, slot_role_id, slot_inherited);
+CREATE INDEX slot_revision_origin_role ON slots (slot_revision_id, slot_origin, slot_role_id);
CREATE SEQUENCE content_content_id_seq;
--- /dev/null
+--
+-- Replace slot_inherited with slot_origin.
+--
+-- NOTE: There is no release that has slot_inherited. This is only needed to transition between
+-- snapshot versions of 1.30.
+--
+-- NOTE: No code that writes to the slots table was merge yet, the table is assumed to be empty.
+--
+BEGIN TRANSACTION;
+
+DROP TABLE /*_*/slots;
+
+CREATE TABLE /*_*/slots (
+
+ -- reference to rev_id
+ slot_revision_id bigint unsigned NOT NULL,
+
+ -- reference to role_id
+ slot_role_id smallint unsigned NOT NULL,
+
+ -- reference to content_id
+ slot_content_id bigint unsigned NOT NULL,
+
+ -- The revision ID of the revision that originated the slot's content.
+ -- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ slot_origin bigint unsigned NOT NULL,
+
+ PRIMARY KEY ( slot_revision_id, slot_role_id )
+) /*$wgDBTableOptions*/;
+
+-- Index for finding revisions that modified a specific slot
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
+
+COMMIT TRANSACTION;
\ No newline at end of file
-- reference to content_id
slot_content_id bigint unsigned NOT NULL,
- -- whether the content is inherited (1) or new in this revision (0)
- slot_inherited tinyint unsigned NOT NULL DEFAULT 0,
+ -- The revision ID of the revision that originated the slot's content.
+ -- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ slot_origin bigint unsigned NOT NULL,
PRIMARY KEY ( slot_revision_id, slot_role_id )
) /*$wgDBTableOptions*/;
-- Index for finding revisions that modified a specific slot
-CREATE INDEX /*i*/slot_role_inherited ON /*_*/slots (slot_revision_id, slot_role_id, slot_inherited);
+CREATE INDEX /*i*/slot_revision_origin_role ON /*_*/slots (slot_revision_id, slot_origin, slot_role_id);
--
-- The content table represents content objects. It's primary purpose is to provide the necessary
public function testNewNullRevision( Title $title, $comment, $minor ) {
$store = MediaWikiServices::getInstance()->getRevisionStore();
$user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
+
+ $parent = $store->getRevisionByTitle( $title );
$record = $store->newNullRevision(
wfGetDB( DB_MASTER ),
$title,
$this->assertEquals( $comment, $record->getComment() );
$this->assertEquals( $minor, $record->isMinor() );
$this->assertEquals( $user->getName(), $record->getUser()->getName() );
+ $this->assertEquals( $parent->getId(), $record->getParentId() );
+
+ $parentSlot = $parent->getSlot( 'main' );
+ $slot = $record->getSlot( 'main' );
+
+ $this->assertTrue( $slot->isInherited(), 'isInherited' );
+ $this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' );
+ $this->assertSame( $parentSlot->getAddress(), $slot->getAddress(), 'getAddress' );
}
/**
'model_name' => CONTENT_MODEL_WIKITEXT,
'format_name' => CONTENT_FORMAT_WIKITEXT,
'slot_revision_id' => '2',
- 'slot_inherited' => '1',
+ 'slot_origin' => '1',
'role_name' => 'myRole',
];
return (object)$data;
$this->assertSame( 'someHash', $record->getSha1() );
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
$this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 1, $record->getOrigin() );
$this->assertSame( 'tt:456', $record->getAddress() );
$this->assertSame( 33, $record->getContentId() );
$this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
'format_name' => function () {
return CONTENT_FORMAT_WIKITEXT;
},
- 'slot_inherited' => '0'
+ 'slot_revision_id' => '2',
+ 'slot_origin' => '2',
] );
$content = function () {
$this->assertNotNull( $record->getSha1() );
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
$this->assertSame( 2, $record->getRevision() );
+ $this->assertSame( 2, $record->getRevision() );
$this->assertSame( 'tt:456', $record->getAddress() );
$this->assertSame( 33, $record->getContentId() );
$this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
$record->getAddress();
}
- public function testGetRevision_fails() {
+ public function provideIncomplete() {
+ $unsaved = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ yield 'unsaved' => [ $unsaved ];
+
+ $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
+ $inherited = SlotRecord::newInherited( $parent );
+ yield 'inherited' => [ $inherited ];
+ }
+
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetRevision_fails( SlotRecord $record ) {
$record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
$this->setExpectedException( IncompleteRevisionException::class );
$record->getRevision();
}
+ /**
+ * @dataProvider provideIncomplete
+ */
+ public function testGetOrigin_fails( SlotRecord $record ) {
+ $record = SlotRecord::newUnsaved( 'main', new WikitextContent( 'A' ) );
+ $this->setExpectedException( IncompleteRevisionException::class );
+
+ $record->getOrigin();
+ }
+
public function provideHashStability() {
yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
}
public function testNewInherited() {
- $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_inherited' => 0 ] );
+ $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
$parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
// This would happen while doing an edit, before saving revision meta-data.
$this->assertSame( 20, $saved->getContentId() );
$this->assertSame( 'A', $saved->getContent()->getNativeData() );
$this->assertSame( 10, $saved->getRevision() );
+ $this->assertSame( 10, $saved->getOrigin() );
// make sure we didn't mess with the internal state of $unsaved
$this->assertFalse( $unsaved->hasAddress() );
$freshRow = $this->makeRow( [
'content_id' => 10,
'content_address' => 'address:1',
- 'slot_inherited' => 0,
+ 'slot_origin' => 1,
'slot_revision_id' => 1,
] );
$inheritedRow = $this->makeRow( [
'content_id' => null,
'content_address' => null,
- 'slot_inherited' => 1
+ 'slot_origin' => 0,
+ 'slot_revision_id' => 1,
] );
$inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );