CommentStore: Accept SCHEMA_COMPAT_* constants
authorBrad Jorsch <bjorsch@wikimedia.org>
Fri, 20 Sep 2019 18:35:39 +0000 (14:35 -0400)
committerMobrovac <mobrovac@wikimedia.org>
Thu, 26 Sep 2019 11:17:50 +0000 (11:17 +0000)
Extensions doing an actor and comment migration at the same time will
likely want to use the same constants for each, and that will most
likely be using write-both/read-old and write-both/read-new for the
middle stages rather than write-both/read-both and write-new/read-both
as implemented by the MIGRATION_* constants. See Ie29fd05 for example.

This patch changes CommentStore's internal logic to work correctly when
passed write-both/read-old or write-both/read-new.

Bug: T233449
Change-Id: Iebec80f969ad1c4f9f51a4f25656319cca32b0dd

includes/CommentStore.php
tests/phpunit/includes/CommentStoreTest.php

index 994a064..9054e7a 100644 (file)
@@ -83,7 +83,8 @@ class CommentStore {
        protected $key = null;
 
        /**
-        * @var int One of the MIGRATION_* constants
+        * @var int One of the MIGRATION_* constants, or an appropriate combination
+        *  of SCHEMA_COMPAT_* constants.
         * @todo Deprecate and remove once extensions seem unlikely to need to use
         *  it for migration anymore.
         */
@@ -98,11 +99,19 @@ class CommentStore {
        /**
         * @param Language $lang Language to use for comment truncation. Defaults
         *  to content language.
-        * @param int $migrationStage One of the MIGRATION_* constants. Always
-        *  MIGRATION_NEW for MediaWiki core since 1.33.
+        * @param int $stage One of the MIGRATION_* constants, or an appropriate
+        *  combination of SCHEMA_COMPAT_* constants. Always MIGRATION_NEW for
+        *  MediaWiki core since 1.33.
         */
-       public function __construct( Language $lang, $migrationStage ) {
-               $this->stage = $migrationStage;
+       public function __construct( Language $lang, $stage ) {
+               if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a write mode' );
+               }
+               if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) {
+                       throw new InvalidArgumentException( '$stage must include a read mode' );
+               }
+
+               $this->stage = $stage;
                $this->lang = $lang;
        }
 
@@ -166,21 +175,21 @@ class CommentStore {
        public function getFields( $key = null ) {
                $key = $this->getKey( $key );
                $fields = [];
-               if ( $this->stage === MIGRATION_OLD ) {
+               if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                        $fields["{$key}_text"] = $key;
                        $fields["{$key}_data"] = 'NULL';
                        $fields["{$key}_cid"] = 'NULL';
-               } else {
-                       if ( $this->stage < MIGRATION_NEW ) {
+               } else { // READ_BOTH or READ_NEW
+                       if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_old"] = $key;
                        }
 
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                       if ( $tempTableStage < MIGRATION_NEW ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_pk"] = $this->tempTables[$key]['joinPK'];
                        }
-                       if ( $tempTableStage > MIGRATION_OLD ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) {
                                $fields["{$key}_id"] = "{$key}_id";
                        }
                }
@@ -211,21 +220,21 @@ class CommentStore {
                        $fields = [];
                        $joins = [];
 
-                       if ( $this->stage === MIGRATION_OLD ) {
+                       if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                                $fields["{$key}_text"] = $key;
                                $fields["{$key}_data"] = 'NULL';
                                $fields["{$key}_cid"] = 'NULL';
-                       } else {
-                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
+                       } else { // READ_BOTH or READ_NEW
+                               $join = ( $this->stage & SCHEMA_COMPAT_READ_OLD ) ? 'LEFT JOIN' : 'JOIN';
 
                                $tempTableStage = isset( $this->tempTables[$key] )
                                        ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                               if ( $tempTableStage < MIGRATION_NEW ) {
+                               if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) {
                                        $t = $this->tempTables[$key];
                                        $alias = "temp_$key";
                                        $tables[$alias] = $t['table'];
                                        $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
-                                       if ( $tempTableStage === MIGRATION_OLD ) {
+                                       if ( ( $tempTableStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                                                $joinField = "{$alias}.{$t['field']}";
                                        } else {
                                                // Nothing hits this code path for now, but will in the future when we set
@@ -245,7 +254,7 @@ class CommentStore {
                                $tables[$alias] = 'comment';
                                $joins[$alias] = [ $join, "{$alias}.comment_id = {$joinField}" ];
 
-                               if ( $this->stage === MIGRATION_NEW ) {
+                               if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_NEW ) {
                                        $fields["{$key}_text"] = "{$alias}.comment_text";
                                } else {
                                        $fields["{$key}_text"] = "COALESCE( {$alias}.comment_text, $key )";
@@ -282,13 +291,15 @@ class CommentStore {
                        $cid = $row["{$key}_cid"] ?? null;
                        $text = $row["{$key}_text"];
                        $data = $row["{$key}_data"];
-               } elseif ( $this->stage === MIGRATION_OLD ) {
+               } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) {
                        $cid = null;
                        if ( $fallback && isset( $row[$key] ) ) {
                                wfLogWarning( "Using deprecated fallback handling for comment $key" );
                                $text = $row[$key];
                        } else {
-                               wfLogWarning( "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD" );
+                               wfLogWarning(
+                                       "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD / READ_OLD"
+                               );
                                $text = '';
                        }
                        $data = null;
@@ -296,7 +307,7 @@ class CommentStore {
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
                        $row2 = null;
-                       if ( $tempTableStage > MIGRATION_OLD && array_key_exists( "{$key}_id", $row ) ) {
+                       if ( ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) && array_key_exists( "{$key}_id", $row ) ) {
                                if ( !$db ) {
                                        throw new InvalidArgumentException(
                                                "\$row does not contain fields needed for comment $key and getComment(), but "
@@ -311,7 +322,9 @@ class CommentStore {
                                        __METHOD__
                                );
                        }
-                       if ( !$row2 && $tempTableStage < MIGRATION_NEW && array_key_exists( "{$key}_pk", $row ) ) {
+                       if ( !$row2 && ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) &&
+                               array_key_exists( "{$key}_pk", $row )
+                       ) {
                                if ( !$db ) {
                                        throw new InvalidArgumentException(
                                                "\$row does not contain fields needed for comment $key and getComment(), but "
@@ -341,7 +354,9 @@ class CommentStore {
                                $cid = $row2->comment_id;
                                $text = $row2->comment_text;
                                $data = $row2->comment_data;
-                       } elseif ( $this->stage < MIGRATION_NEW && array_key_exists( "{$key}_old", $row ) ) {
+                       } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_OLD ) &&
+                               array_key_exists( "{$key}_old", $row )
+                       ) {
                                $cid = null;
                                $text = $row["{$key}_old"];
                                $data = null;
@@ -474,7 +489,7 @@ class CommentStore {
                # Truncate comment in a Unicode-sensitive manner
                $comment->text = $this->lang->truncateForVisual( $comment->text, self::COMMENT_CHARACTER_LIMIT );
 
-               if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
+               if ( ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) && !$comment->id ) {
                        $dbData = $comment->data;
                        if ( !$comment->message instanceof RawMessage ) {
                                if ( $dbData === null ) {
@@ -534,14 +549,14 @@ class CommentStore {
 
                $comment = $this->createComment( $dbw, $comment, $data );
 
-               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
                        $fields[$key] = $this->lang->truncateForDatabase( $comment->text, 255 );
                }
 
-               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+               if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
                        $tempTableStage = isset( $this->tempTables[$key] )
                                ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-                       if ( $tempTableStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $t = $this->tempTables[$key];
                                $func = __METHOD__;
                                $commentId = $comment->id;
@@ -556,7 +571,7 @@ class CommentStore {
                                        );
                                };
                        }
-                       if ( $tempTableStage >= MIGRATION_WRITE_BOTH ) {
+                       if ( $tempTableStage & SCHEMA_COMPAT_WRITE_NEW ) {
                                $fields["{$key}_id"] = $comment->id;
                        }
                }
@@ -594,7 +609,7 @@ class CommentStore {
 
                $tempTableStage = isset( $this->tempTables[$key] )
                        ? $this->tempTables[$key]['stage'] : MIGRATION_NEW;
-               if ( $tempTableStage < MIGRATION_WRITE_NEW ) {
+               if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) {
                        throw new InvalidArgumentException( "Must use insertWithTempTable() for $key" );
                }
 
index 9c08b9f..57cc073 100644 (file)
@@ -73,6 +73,47 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                return $store;
        }
 
+       /**
+        * @dataProvider provideConstructor
+        * @param int $stage
+        * @param string|null $exceptionMsg
+        */
+       public function testConstructor( $stage, $exceptionMsg ) {
+               try {
+                       $m = new CommentStore( Language::factory( 'qqx' ), $stage );
+                       if ( $exceptionMsg !== null ) {
+                               $this->fail( 'Expected exception not thrown' );
+                       }
+                       $this->assertInstanceOf( CommentStore::class, $m );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame( $exceptionMsg, $ex->getMessage() );
+               }
+       }
+
+       public static function provideConstructor() {
+               return [
+                       [ 0, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ],
+                       [ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ],
+
+                       [ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, null ],
+
+                       [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, null ],
+
+                       [ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ],
+                       [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, null ],
+               ];
+       }
+
        /**
         * @dataProvider provideGetFields
         * @param int $stage
@@ -115,6 +156,14 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW, 'ipb_reason',
                                [ 'ipb_reason_id' => 'ipb_reason_id' ],
                        ],
+                       'Simple table, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason',
+                               [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
+                       ],
+                       'Simple table, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason',
+                               [ 'ipb_reason_id' => 'ipb_reason_id' ],
+                       ],
 
                        'Revision, old' => [
                                MIGRATION_OLD, 'rev_comment',
@@ -136,6 +185,18 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW, 'rev_comment',
                                [ 'rev_comment_pk' => 'rev_id' ],
                        ],
+                       'Revision, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment',
+                               [
+                                       'rev_comment_text' => 'rev_comment',
+                                       'rev_comment_data' => 'NULL',
+                                       'rev_comment_cid' => 'NULL',
+                               ],
+                       ],
+                       'Revision, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment',
+                               [ 'rev_comment_pk' => 'rev_id' ],
+                       ],
 
                        'Image, old' => [
                                MIGRATION_OLD, 'img_description',
@@ -165,6 +226,20 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        'img_description_id' => 'img_description_id'
                                ],
                        ],
+                       'Image, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description',
+                               [
+                                       'img_description_text' => 'img_description',
+                                       'img_description_data' => 'NULL',
+                                       'img_description_cid' => 'NULL',
+                               ],
+                       ],
+                       'Image, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description',
+                               [
+                                       'img_description_id' => 'img_description_id'
+                               ],
+                       ],
                ];
        }
 
@@ -244,6 +319,30 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Simple table, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'ipb_reason',
+                                               'ipb_reason_data' => 'NULL',
+                                               'ipb_reason_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', [
+                                       'tables' => [ 'comment_ipb_reason' => 'comment' ],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'comment_ipb_reason.comment_text',
+                                               'ipb_reason_data' => 'comment_ipb_reason.comment_data',
+                                               'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
+                                       ],
+                               ],
+                       ],
 
                        'Revision, old' => [
                                MIGRATION_OLD, 'rev_comment', [
@@ -310,6 +409,35 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Revision, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'rev_comment_text' => 'rev_comment',
+                                               'rev_comment_data' => 'NULL',
+                                               'rev_comment_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Revision, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', [
+                                       'tables' => [
+                                               'temp_rev_comment' => 'revision_comment_temp',
+                                               'comment_rev_comment' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'rev_comment_text' => 'comment_rev_comment.comment_text',
+                                               'rev_comment_data' => 'comment_rev_comment.comment_data',
+                                               'rev_comment_cid' => 'comment_rev_comment.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                               'comment_rev_comment' => [ 'JOIN',
+                                                       'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       ],
+                               ],
+                       ],
 
                        'Image, old' => [
                                MIGRATION_OLD, 'img_description', [
@@ -373,6 +501,34 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        ],
                                ],
                        ],
+                       'Image, write-both/read-old' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'img_description_text' => 'img_description',
+                                               'img_description_data' => 'NULL',
+                                               'img_description_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Image, write-both/read-new' => [
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', [
+                                       'tables' => [
+                                               'comment_img_description' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'img_description_text' => 'comment_img_description.comment_text',
+                                               'img_description_data' => 'comment_img_description.comment_data',
+                                               'img_description_cid' => 'comment_img_description.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_img_description' => [ 'JOIN',
+                                                       'comment_img_description.comment_id = img_description_id',
+                                               ],
+                                       ],
+                               ],
+                       ],
                ];
        }
 
@@ -413,6 +569,15 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW ],
                        MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
                        MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
                ];
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
@@ -427,12 +592,12 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                $fields = $wstore->insert( $this->db, $key, $comment, $data );
                        }
 
-                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
                        }
-                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                       if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
                                $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
@@ -463,13 +628,17 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        $queryInfo['joins']
                                );
 
+                               $expectForCombination = (
+                                       ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
+                                       ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
+                               ) ? $expectOld : $expect;
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getCommentLegacy( $this->db, $key, $fieldRow ),
                                        "w=$writeStage, r=$readStage, from getFields()"
                                );
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getComment( $key, $joinRow ),
                                        "w=$writeStage, r=$readStage, from getJoin()"
                                );
@@ -503,6 +672,15 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                MIGRATION_NEW ],
                        MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
                        MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+                               MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+                               SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW
+                       ],
                ];
 
                foreach ( $stages as $writeStage => $possibleReadStages ) {
@@ -517,12 +695,12 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                $fields = $wstore->insert( $this->db, $comment, $data );
                        }
 
-                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                       if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
                                $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
                        }
-                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                       if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
                                $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
                        } else {
                                $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
@@ -553,13 +731,17 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                        $queryInfo['joins']
                                );
 
+                               $expectForCombination = (
+                                       ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD ||
+                                       ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD
+                               ) ? $expectOld : $expect;
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getCommentLegacy( $this->db, $fieldRow ),
                                        "w=$writeStage, r=$readStage, from getFields()"
                                );
                                $this->assertComment(
-                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $expectForCombination,
                                        $rstore->getComment( $joinRow ),
                                        "w=$writeStage, r=$readStage, from getJoin()"
                                );
@@ -725,6 +907,9 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                        'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
                        'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
                        'MIGRATION_NEW' => [ MIGRATION_NEW ],
+
+                       'SCHEMA_COMPAT write-both/read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+                       'SCHEMA_COMPAT write-both/read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
                ];
        }