/**
* @var boolean
+ * @see $wgContentHandlerUseDB
*/
private $contentHandlerUseDB = true;
*/
private $slotRoleStore;
- /** @var int One of the MIGRATION_* constants */
+ /** @var int An appropriate combination of SCHEMA_COMPAT_XXX flags. */
private $mcrMigrationStage;
/**
* @param CommentStore $commentStore
* @param NameTableStore $contentModelStore
* @param NameTableStore $slotRoleStore
- * @param int $migrationStage
+ * @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
* @param bool|string $wikiId
+ *
+ * @throws MWException if $mcrMigrationStage or $wikiId is invalid.
*/
public function __construct(
LoadBalancer $loadBalancer,
CommentStore $commentStore,
NameTableStore $contentModelStore,
NameTableStore $slotRoleStore,
- $migrationStage,
+ $mcrMigrationStage,
ActorMigration $actorMigration,
$wikiId = false
) {
Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
- Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
+ Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
+ '$mcrMigrationStage',
+ 'Reading from the old and the new schema at the same time is not supported.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Reading needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Writing needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_OLD ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the old schema when not also writing it.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_NEW ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the new schema when not also writing it.'
+ );
$this->loadBalancer = $loadBalancer;
$this->blobStore = $blobStore;
$this->commentStore = $commentStore;
$this->contentModelStore = $contentModelStore;
$this->slotRoleStore = $slotRoleStore;
- $this->mcrMigrationStage = $migrationStage;
+ $this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
$this->wikiId = $wikiId;
$this->logger = new NullLogger();
}
+ /**
+ * @param int $flags A combination of SCHEMA_COMPAT_XXX flags.
+ * @return bool True if all the given flags were set in the $mcrMigrationStage
+ * parameter passed to the constructor.
+ */
+ private function hasMcrSchemaFlags( $flags ) {
+ return ( $this->mcrMigrationStage & $flags ) === $flags;
+ }
+
public function setLogger( LoggerInterface $logger ) {
$this->logger = $logger;
}
}
/**
+ * @see $wgContentHandlerUseDB
* @param bool $contentHandlerUseDB
* @throws MWException
*/
public function setContentHandlerUseDB( $contentHandlerUseDB ) {
- if ( !$contentHandlerUseDB && $this->mcrMigrationStage > MIGRATION_OLD ) {
- throw new MWException(
- 'Content model must be stored in the database for multi content revision migration.'
- );
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW )
+ || $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW )
+ ) {
+ if ( !$contentHandlerUseDB ) {
+ throw new MWException(
+ 'Content model must be stored in the database for multi content revision migration.'
+ );
+ }
}
$this->contentHandlerUseDB = $contentHandlerUseDB;
}
}
// While inserting into the old schema make sure only the main slot is allowed.
- // TODO: support extra slots in MIGRATION_WRITE_BOTH mode!
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH && $slotRoles !== [ 'main' ] ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) && $slotRoles !== [ 'main' ] ) {
throw new InvalidArgumentException(
- 'Only the main slot is supported with MCR migration mode <= MIGRATION_WRITE_BOTH!'
+ 'Only the main slot is supported when writing to the pre-MCR schema!'
);
}
);
// Trigger exception if the main slot is missing.
- // Technically, this could go away with MIGRATION_NEW: while
+ // Technically, this could go away after MCR migration: while
// calling code may require a main slot to exist, RevisionStore
// really should not know or care about that requirement.
$rev->getSlot( 'main', RevisionRecord::RAW );
$newSlots[$role] = $slot;
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $slot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $slot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$blobAddress = $slot->getAddress();
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
}
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $protoSlot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $protoSlot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
- if ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
if ( $protoSlot->hasContentId() ) {
$contentId = $protoSlot->getContentId();
} else {
$revisionRow['rev_id'] = $rev->getId();
}
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
- // In non MCR more this IF section will relate to the main slot
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
+ // In non MCR mode this IF section will relate to the main slot
$mainSlot = $rev->getSlot( 'main' );
$model = $mainSlot->getModel();
$format = $mainSlot->getFormat();
$blobFlags = null;
if ( is_object( $row ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
// Don't emulate from a row when using the new schema.
// Emulating from an array is still OK.
throw new LogicException( 'Can\'t emulate the main slot when using MCR schema.' );
$queryFlags,
Title $title
) {
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
- // TODO: in MIGRATION_WRITE_BOTH, we could use the old and the new method:
- // e.g. call emulateMainSlot_1_29() if loadSlotRecords() fails.
-
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
$mainSlot = $this->emulateMainSlot_1_29( $revisionRow, $queryFlags, $title );
$slots = new RevisionSlots( [ 'main' => $mainSlot ] );
} else {
}
if ( !empty( $fields['text_id'] ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
- throw new MWException( "Cannot use text_id field with MCR schema" );
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ throw new MWException( "The text_id field is only available in the pre-MCR schema" );
}
if ( !empty( $fields['content'] ) ) {
/**
* Finds the ID of a content row for a given revision and slot role.
* This can be used to re-use content rows even while the content ID
- * is still missing from SlotRecords, in MIGRATION_WRITE_BOTH mode.
+ * is still missing from SlotRecords, when writing to both the old and
+ * the new schema during MCR schema migration.
*
* @todo remove after MCR schema migration is complete.
*
* @return int|null
*/
private function findSlotContentId( IDatabase $db, $revId, $role ) {
- if ( $this->mcrMigrationStage < MIGRATION_WRITE_BOTH ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
return null;
}
* - 'page': Join with the page table, and select fields to identify the page
* - 'user': Join with the user table, and select the user name
* - 'text': Join with the text table, and select fields to load page text. This
- * option is deprecated in MW 1.32 with MCR migration stage MIGRATION_WRITE_BOTH,
- * and disallowed with MIGRATION_MEW.
+ * option is deprecated in MW 1.32 when the MCR migration flag SCHEMA_COMPAT_WRITE_NEW
+ * is set, and disallowed when SCHEMA_COMPAT_READ_OLD is not set.
*
* @return array With three keys:
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
$ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
$ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'rev_text_id';
if ( $this->contentHandlerUseDB ) {
}
if ( in_array( 'text', $options, true ) ) {
- if ( $this->mcrMigrationStage === MIGRATION_NEW ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
throw new InvalidArgumentException( 'text table can no longer be joined directly' );
- } elseif ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ } elseif ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ // NOTE: even when this class is set to not read from the old schema, callers
+ // should still be able to join against the text table, as long as we are still
+ // writing the old schema for compatibility.
wfDeprecated( __METHOD__ . ' with `text` option', '1.32' );
}
'joins' => [],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$db = $this->getDBConnectionRef( DB_REPLICA );
$ret['tables']['slots'] = 'revision';
$ret['fields']['model_name'] = 'NULL';
}
}
-
- // XXX: in MIGRATION_WRITE_BOTH mode, emulate *and* select - using a UNION?
- // See Anomie's idea at <https://gerrit.wikimedia.org/r/c/416465/
- // 8..10/includes/Storage/RevisionStore.php#2113>
} else {
$ret['tables'][] = 'slots';
$ret['tables'][] = 'slot_roles';
'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'ar_text_id';
if ( $this->contentHandlerUseDB ) {
$updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
if ( !$updater->isUpdatePrepared() ) {
- $updater->prepareContent( $user, $slots, [], $useCache );
+ $updater->prepareContent( $user, $slots, $useCache );
if ( $revision ) {
$updater->prepareUpdate( $revision );
// Note array_intersect() preserves keys from the first arg, and we're
// assuming $revQuery has `revision` primary and isn't using subtables
// for anything we care about.
- $tablesFlat = [];
- array_walk_recursive(
- $revQuery['tables'],
- function ( $a ) use ( &$tablesFlat ) {
- $tablesFlat[] = $a;
- }
- );
-
$res = $dbw->select(
array_intersect(
- $tablesFlat,
+ $revQuery['tables'],
[ 'revision', 'revision_comment_temp', 'revision_actor_temp' ]
),
'1',
// Fetch all rows in case the DB needs that to properly lock them.
}
- // Get all of the page revisions
+ // If SCHEMA_COMPAT_WRITE_OLD is set, also select all extra fields we still write,
+ // so we can copy it to the archive table.
+ // We know the fields exist, otherwise SCHEMA_COMPAT_WRITE_OLD could not function.
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ $revQuery['fields'][] = 'rev_text_id';
+
+ if ( $wgContentHandlerUseDB ) {
+ $revQuery['fields'][] = 'rev_content_model';
+ $revQuery['fields'][] = 'rev_content_format';
+ }
+ }
+
+ // Get all of the page revisions
$res = $dbw->select(
$revQuery['tables'],
$revQuery['fields'],
] + $commentStore->insert( $dbw, 'ar_comment', $comment )
+ $actorMigration->getInsertValues( $dbw, 'ar_user', $user );
- if ( $wgMultiContentRevisionSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$rowInsert['ar_text_id'] = $row->rev_text_id;
- }
- if (
- $wgContentHandlerUseDB &&
- $wgMultiContentRevisionSchemaMigrationStage <= MIGRATION_WRITE_BOTH
- ) {
- $rowInsert['ar_content_model'] = $row->rev_content_model;
- $rowInsert['ar_content_format'] = $row->rev_content_format;
+ if ( $wgContentHandlerUseDB ) {
+ $rowInsert['ar_content_model'] = $row->rev_content_model;
+ $rowInsert['ar_content_format'] = $row->rev_content_format;
+ }
}
+
$rowsInsert[] = $rowInsert;
$revids[] = $row->rev_id;
}
$updater->setOriginalRevisionId( $target->getId() );
- $updater->setUndidRevisionId( $current->getId() );
+ // Do not call setUndidRevisionId(), that causes an extra "mw-undo" tag to be added (T190374)
$updater->addTags( $tags );
+ // TODO: this logic should not be in the storage layer, it's here for compatibility
+ // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
+ // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
+ if ( $wgUseRCPatrol && $this->getTitle()->userCan( 'autopatrol', $guser ) ) {
+ $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
+ }
+
// Actually store the rollback
$rev = $updater->saveRevision(
CommentStoreComment::newUnsavedComment( $summary ),
*/
public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
$id = $id ?: $this->getId();
- $ns = $this->getTitle()->getNamespace();
+ $type = MWNamespace::getCategoryLinkType( $this->getTitle()->getNamespace() );
$addFields = [ 'cat_pages = cat_pages + 1' ];
$removeFields = [ 'cat_pages = cat_pages - 1' ];
- if ( $ns == NS_CATEGORY ) {
- $addFields[] = 'cat_subcats = cat_subcats + 1';
- $removeFields[] = 'cat_subcats = cat_subcats - 1';
- } elseif ( $ns == NS_FILE ) {
- $addFields[] = 'cat_files = cat_files + 1';
- $removeFields[] = 'cat_files = cat_files - 1';
+ if ( $type !== 'page' ) {
+ $addFields[] = "cat_{$type}s = cat_{$type}s + 1";
+ $removeFields[] = "cat_{$type}s = cat_{$type}s - 1";
}
$dbw = wfGetDB( DB_MASTER );
$insertRows[] = [
'cat_title' => $cat,
'cat_pages' => 1,
- 'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
- 'cat_files' => ( $ns == NS_FILE ) ? 1 : 0,
+ 'cat_subcats' => ( $type === 'subcat' ) ? 1 : 0,
+ 'cat_files' => ( $type === 'file' ) ? 1 : 0,
];
}
$dbw->upsert(