Merge "Introduce new schema flags and use them in RevisionStore."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 6 Jul 2018 17:53:44 +0000 (17:53 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 6 Jul 2018 17:53:44 +0000 (17:53 +0000)
1  2 
includes/DefaultSettings.php
includes/Storage/RevisionStore.php
includes/page/WikiPage.php

@@@ -3003,6 -3003,11 +3003,6 @@@ $wgAllUnicodeFixes = false
   */
  $wgLegacyEncoding = false;
  
 -/**
 - * @deprecated since 1.30, does nothing
 - */
 -$wgBrowserBlackList = [];
 -
  /**
   * If set to true, the MediaWiki 1.4 to 1.5 schema conversion will
   * create stub reference rows in the text table instead of copying
@@@ -7887,7 -7892,7 +7887,7 @@@ $wgNewUserLog = true
   * Maintain a log of page creations at Special:Log/create?
   * @since 1.32
   */
 -$wgPageCreationLog = false;
 +$wgPageCreationLog = true;
  
  /** @} */ # end logging }
  
@@@ -8892,13 -8897,23 +8892,23 @@@ $wgInterwikiPrefixDisplayTypes = []
  $wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
  
  /**
-  * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables)
+  * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
+  * Use the SCHEMA_COMPAT_XXX flags. Supported values:
+  *
+  * - SCHEMA_COMPAT_OLD
+  * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+  * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+  * - SCHEMA_COMPAT_OLD
+  *
+  * Note that reading the old and new schema at the same time is not supported.
+  * Attempting to set both read bits in $wgMultiContentRevisionSchemaMigrationStage
+  * will result in an InvalidArgumentException.
   *
   * @see Task: https://phabricator.wikimedia.org/T174028
   * @see Commit: https://gerrit.wikimedia.org/r/#/c/378724/
   *
   * @since 1.32
-  * @var int One of the MIGRATION_* constants
+  * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
   */
  $wgMultiContentRevisionSchemaMigrationStage = MIGRATION_OLD;
  
@@@ -83,7 -83,6 +83,7 @@@ class RevisionStor
  
        /**
         * @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 ) {
@@@ -1935,7 -1935,7 +1935,7 @@@ class WikiPage implements Page, IDBAcce
                $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(