Merge "Introduce RevisionRecord::isReadForInsertion"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 11 Sep 2018 15:14:17 +0000 (15:14 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 11 Sep 2018 15:14:17 +0000 (15:14 +0000)
1  2 
includes/Storage/RevisionStore.php

@@@ -466,6 -466,12 +466,12 @@@ class RevisionStor
                $this->failOnNull( $user->getId(), 'user field' );
                $this->failOnEmpty( $user->getName(), 'user_text field' );
  
+               if ( !$rev->isReadyForInsertion() ) {
+                       // This is here for future-proofing. At the time this check being added, it
+                       // was redundant to the individual checks above.
+                       throw new IncompleteRevisionException( 'Revision is incomplete' );
+               }
                // TODO: we shouldn't need an actual Title here.
                $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
                $pageId = $this->failOnEmpty( $rev->getPageId(), 'rev_page field' ); // check this early
                foreach ( $slotRoles as $role ) {
                        $slot = $rev->getSlot( $role, RevisionRecord::RAW );
  
 -                      if ( $slot->hasRevision() ) {
 -                              // If the SlotRecord already has a revision ID set, this means it already exists
 -                              // in the database, and should already belong to the current revision.
 +                      // If the SlotRecord already has a revision ID set, this means it already exists
 +                      // in the database, and should already belong to the current revision.
 +                      // However, a slot may already have a revision, but no content ID, if the slot
 +                      // is emulated based on the archive table, because we are in SCHEMA_COMPAT_READ_OLD
 +                      // mode, and the respective archive row was not yet migrated to the new schema.
 +                      // In that case, a new slot row (and content row) must be inserted even during
 +                      // undeletion.
 +                      if ( $slot->hasRevision() && $slot->hasContentId() ) {
                                // TODO: properly abort transaction if the assertion fails!
                                Assert::parameter(
                                        $slot->getRevision() === $revisionId,
         * @param IDatabase $dbw
         * @param int $revisionId
         * @param string &$blobAddress (may change!)
 +       *
 +       * @return int the text row id
         */
        private function updateRevisionTextId( IDatabase $dbw, $revisionId, &$blobAddress ) {
                $textId = $this->blobStore->getTextIdFromAddress( $blobAddress );
                        [ 'rev_id' => $revisionId ],
                        __METHOD__
                );
 +
 +              return $textId;
        }
  
        /**
                        $blobAddress = $this->storeContentBlob( $protoSlot, $title, $blobHints );
                }
  
 +              $contentId = null;
 +
                // Write the main slot's text ID to the revision table for backwards compatibility
                if ( $protoSlot->getRole() === 'main'
                        && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
                ) {
 -                      $this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
 +                      // If SCHEMA_COMPAT_WRITE_NEW is also set, the fake content ID is overwritten
 +                      // with the real content ID below.
 +                      $textId = $this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
 +                      $contentId = $this->emulateContentId( $textId );
                }
  
                if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
                        }
  
                        $this->insertSlotRowOn( $protoSlot, $dbw, $revisionId, $contentId );
 -              } else {
 -                      $contentId = null;
                }
  
                $savedSlot = SlotRecord::newSaved(
                $mainSlotRow->role_name = 'main';
                $mainSlotRow->model_name = null;
                $mainSlotRow->slot_revision_id = null;
 +              $mainSlotRow->slot_content_id = null;
                $mainSlotRow->content_address = null;
  
                $content = null;
                        $mainSlotRow->format_name = isset( $row->rev_content_format )
                                ? strval( $row->rev_content_format )
                                : null;
 +
 +                      if ( isset( $row->rev_text_id ) && intval( $row->rev_text_id ) > 0 ) {
 +                              // Overwritten below for SCHEMA_COMPAT_WRITE_NEW
 +                              $mainSlotRow->slot_content_id
 +                                      = $this->emulateContentId( intval( $row->rev_text_id ) );
 +                      }
                } elseif ( is_array( $row ) ) {
                        $mainSlotRow->slot_revision_id = isset( $row['id'] ) ? intval( $row['id'] ) : null;
  
                                        $mainSlotRow->format_name = $handler->getDefaultFormat();
                                }
                        }
 +
 +                      if ( isset( $row['text_id'] ) && intval( $row['text_id'] ) > 0 ) {
 +                              // Overwritten below for SCHEMA_COMPAT_WRITE_NEW
 +                              $mainSlotRow->slot_content_id
 +                                      = $this->emulateContentId( intval( $row['text_id'] ) );
 +                      }
                } else {
                        throw new MWException( 'Revision constructor passed invalid row format.' );
                }
                        };
                }
  
 -              // NOTE: this callback will be looped through RevisionSlot::newInherited(), allowing
 -              // the inherited slot to have the same content_id as the original slot. In that case,
 -              // $slot will be the inherited slot, while $mainSlotRow still refers to the original slot.
 -              $mainSlotRow->slot_content_id =
 -                      function ( SlotRecord $slot ) use ( $queryFlags, $mainSlotRow ) {
 -                              $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
 -                              return $this->findSlotContentId( $db, $mainSlotRow->slot_revision_id, 'main' );
 -                      };
 +              if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
 +                      // NOTE: this callback will be looped through RevisionSlot::newInherited(), allowing
 +                      // the inherited slot to have the same content_id as the original slot. In that case,
 +                      // $slot will be the inherited slot, while $mainSlotRow still refers to the original slot.
 +                      $mainSlotRow->slot_content_id =
 +                              function ( SlotRecord $slot ) use ( $queryFlags, $mainSlotRow ) {
 +                                      $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
 +                                      return $this->findSlotContentId( $db, $mainSlotRow->slot_revision_id, 'main' );
 +                              };
 +              }
  
                return new SlotRecord( $mainSlotRow, $content );
        }
  
 +      /**
 +       * Provides a content ID to use with emulated SlotRecords in SCHEMA_COMPAT_OLD mode,
 +       * based on the revision's text ID (rev_text_id or ar_text_id, respectively).
 +       * Note that in SCHEMA_COMPAT_WRITE_BOTH, a callback to findSlotContentId() should be used
 +       * instead, since in that mode, some revision rows may already have a real content ID,
 +       * while other's don't - and for the ones that don't, we should indicate that it
 +       * is missing and cause SlotRecords::hasContentId() to return false.
 +       *
 +       * @param int $textId
 +       * @return int The emulated content ID
 +       */
 +      private function emulateContentId( $textId ) {
 +              // Return a negative number to ensure the ID is distinct from any real content IDs
 +              // that will be assigned in SCHEMA_COMPAT_WRITE_NEW mode and read in SCHEMA_COMPAT_READ_NEW
 +              // mode.
 +              return -$textId;
 +      }
 +
        /**
         * Loads a Content object based on a slot row.
         *