old and the new schema, but reading the new schema, so Multi-Content Revisions
(MCR) are now functional per default. The new default value of the setting is
SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW.
+* $wgActorTableSchemaMigrationStage no longer accepts MIGRATION_WRITE_BOTH or
+ MIGRATION_WRITE_NEW. It instead uses SCHEMA_COMPAT_WRITE_BOTH |
+ SCHEMA_COMPAT_READ_OLD and SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+ for intermediate stages of migration.
==== Removed configuration ====
* $wgEnableAPI and $wgEnableWriteAPI – These settings, deprecated in 1.31,
RevisionArchiveRecord, RevisionFactory, RevisionLookup, RevisionRecord,
RevisionSlots, RevisionStore, RevisionStoreRecord, SlotRecord, and
SuppressedDataException.
+* When using OOUI HTMLForm containing an 'info' field which uses the 'rawrow'
+ option, it is now deprecated to give its contents (the 'default' option)
+ as a string. They should be given as a OOUI\FieldLayout object instead.
+ Notably, this affects fields defined in the 'GetPreferences' hook, because
+ Special:Preferences uses an OOUI form now. (If possible, don't use 'rawrow'.)
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
*/
class ActorMigration {
+ /**
+ * Constant for extensions to feature-test whether $wgActorTableSchemaMigrationStage
+ * expects MIGRATION_* or SCHEMA_COMPAT_*
+ */
+ const MIGRATION_STAGE_SCHEMA_COMPAT = 1;
+
/**
* Define fields that use temporary tables for transitional purposes
* @var array Keys are '$key', values are arrays with four fields:
/** @var array|null Cache for `self::getJoin()` */
private $joinCache = null;
- /** @var int One of the MIGRATION_* constants */
+ /** @var int Combination of SCHEMA_COMPAT_* constants */
private $stage;
/** @private */
public function __construct( $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' );
+ }
+ if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_BOTH ) {
+ throw new InvalidArgumentException( 'Cannot read both schemas' );
+ }
+ if ( ( $stage & SCHEMA_COMPAT_READ_OLD ) && !( $stage & SCHEMA_COMPAT_WRITE_OLD ) ) {
+ throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
+ }
+ if ( ( $stage & SCHEMA_COMPAT_READ_NEW ) && !( $stage & SCHEMA_COMPAT_WRITE_NEW ) ) {
+ throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
+ }
+
$this->stage = $stage;
}
* @return string
*/
public function isAnon( $field ) {
- return $this->stage === MIGRATION_NEW ? "$field IS NULL" : "$field = 0";
+ return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NULL" : "$field = 0";
}
/**
* @return string
*/
public function isNotAnon( $field ) {
- return $this->stage === MIGRATION_NEW ? "$field IS NOT NULL" : "$field != 0";
+ return ( $this->stage & SCHEMA_COMPAT_READ_NEW ) ? "$field IS NOT NULL" : "$field != 0";
}
/**
list( $text, $actor ) = self::getFieldNames( $key );
- if ( $this->stage === MIGRATION_OLD ) {
+ if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) {
$fields[$key] = $key;
$fields[$text] = $text;
$fields[$actor] = 'NULL';
} else {
- $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
-
if ( isset( self::$tempTables[$key] ) ) {
$t = self::$tempTables[$key];
$alias = "temp_$key";
$tables[$alias] = $t['table'];
- $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+ $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
$joinField = "{$alias}.{$t['field']}";
} else {
$joinField = $actor;
$alias = "actor_$key";
$tables[$alias] = 'actor';
- $joins[$alias] = [ $join, "{$alias}.actor_id = {$joinField}" ];
+ $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
- if ( $this->stage === MIGRATION_NEW ) {
- $fields[$key] = "{$alias}.actor_user";
- $fields[$text] = "{$alias}.actor_name";
- } else {
- $fields[$key] = "COALESCE( {$alias}.actor_user, $key )";
- $fields[$text] = "COALESCE( {$alias}.actor_name, $text )";
- }
+ $fields[$key] = "{$alias}.actor_user";
+ $fields[$text] = "{$alias}.actor_name";
$fields[$actor] = $joinField;
}
list( $text, $actor ) = self::getFieldNames( $key );
$ret = [];
- if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+ if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
$ret[$key] = $user->getId();
$ret[$text] = $user->getName();
}
- if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+ if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
// We need to be able to assign an actor ID if none exists
if ( !$user instanceof User && !$user->getActorId() ) {
$user = User::newFromAnyId( $user->getId(), $user->getName(), null );
list( $text, $actor ) = self::getFieldNames( $key );
$ret = [];
$callback = null;
- if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+ if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) {
$ret[$key] = $user->getId();
$ret[$text] = $user->getName();
}
- if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+ if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) {
// We need to be able to assign an actor ID if none exists
if ( !$user instanceof User && !$user->getActorId() ) {
$user = User::newFromAnyId( $user->getId(), $user->getName(), null );
* - orconds: (array[]) array of alternatives in case a union of multiple
* queries would be more efficient than a query with OR. May have keys
* 'actor', 'userid', 'username'.
+ * Since 1.32, this is guaranteed to contain just one alternative if
+ * $users contains a single user.
* - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
* All tables and joins are aliased, so `+` is safe to use.
*/
list( $text, $actor ) = self::getFieldNames( $key );
// Combine data into conditions to be ORed together
- $actorNotEmpty = [];
- if ( $this->stage === MIGRATION_OLD ) {
- $actors = [];
- $actorEmpty = [];
- } elseif ( isset( self::$tempTables[$key] ) ) {
- $t = self::$tempTables[$key];
- $alias = "temp_$key";
- $tables[$alias] = $t['table'];
- $joins[$alias] = [
- $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
- "{$alias}.{$t['pk']} = {$t['joinPK']}"
- ];
- $joinField = "{$alias}.{$t['field']}";
- $actorEmpty = [ $joinField => null ];
- if ( $this->stage !== MIGRATION_NEW ) {
- // Otherwise the resulting test can evaluate to NULL, and
- // NOT(NULL) is NULL rather than true.
- $actorNotEmpty = [ "$joinField IS NOT NULL" ];
+ if ( $this->stage & SCHEMA_COMPAT_READ_NEW ) {
+ if ( $actors ) {
+ if ( isset( self::$tempTables[$key] ) ) {
+ $t = self::$tempTables[$key];
+ $alias = "temp_$key";
+ $tables[$alias] = $t['table'];
+ $joins[$alias] = [ 'JOIN', "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+ $joinField = "{$alias}.{$t['field']}";
+ } else {
+ $joinField = $actor;
+ }
+ $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
}
} else {
- $joinField = $actor;
- $actorEmpty = [ $joinField => 0 ];
- }
-
- if ( $actors ) {
- $conds['actor'] = $db->makeList(
- $actorNotEmpty + [ $joinField => $actors ], IDatabase::LIST_AND
- );
- }
- if ( $this->stage < MIGRATION_NEW && $ids ) {
- $conds['userid'] = $db->makeList(
- $actorEmpty + [ $key => $ids ], IDatabase::LIST_AND
- );
- }
- if ( $this->stage < MIGRATION_NEW && $names ) {
- $conds['username'] = $db->makeList(
- $actorEmpty + [ $text => $names ], IDatabase::LIST_AND
- );
+ if ( $ids ) {
+ $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
+ }
+ if ( $names ) {
+ $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
+ }
}
return [
public static function selectFields() {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->ipb_by or $row->ipb_by_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
'ipb_address',
'ipb_by',
'ipb_by_text',
- 'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : 'NULL',
+ 'ipb_by_actor' => 'NULL',
'ipb_timestamp',
'ipb_auto',
'ipb_anon_only',
/**
* Actor table schema migration stage.
+ *
+ * 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_NEW
+ *
+ * Note that reading the old and new schema at the same time is not supported
+ * in 1.32, but was (with significant query performance issues) in 1.31.
+ *
* @since 1.31
- * @var int One of the MIGRATION_* constants
+ * @since 1.32 changed allowed flags
+ * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
*/
-$wgActorTableSchemaMigrationStage = MIGRATION_OLD;
+$wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_OLD;
/**
* Temporary option to disable the date picker from the Expiry Widget.
$this->contentFormat,
$ex->getMessage()
);
- $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
}
}
$this->contentFormat,
$ex->getMessage()
);
- $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
}
}
}
protected function consumeNoFork() {
while ( !feof( $this->input ) ) {
$data = fgets( $this->input );
- if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+ if ( substr( $data, -1 ) === "\n" ) {
+ // Strip any final new line used to delimit lines of input.
+ // The last line of input might not have it, though.
$data = substr( $data, 0, -1 );
}
- if ( strlen( $data ) !== 0 ) {
- $result = call_user_func( $this->workCallback, $data );
- fwrite( $this->output, "$result\n" );
+ if ( $data === '' ) {
+ continue;
}
+ $result = call_user_func( $this->workCallback, $data );
+ fwrite( $this->output, "$result\n" );
}
}
$this->updateAvailableSockets( $sockets, $used, $sockets ? 0 : 5 );
} while ( !$sockets );
}
- // Strip the trailing \n. The last line of a file might not have a trailing
- // \n though
- if ( $data[ strlen( $data ) - 1 ] == "\n" ) {
+ if ( substr( $data, - 1 ) === "\n" ) {
+ // Strip any final new line used to delimit lines of input.
+ // The last line of input might not have it, though.
$data = substr( $data, 0, -1 );
}
- if ( strlen( $data ) === 0 ) {
+ if ( $data === '' ) {
continue;
}
$socket = array_pop( $sockets );
global $wgActorTableSchemaMigrationStage;
wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's
// no way the join it's trying to do can work once the old fields
- // aren't being written anymore.
+ // aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
global $wgMultiContentRevisionSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->rev_user or $row->rev_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
global $wgMultiContentRevisionSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->ar_user or $row->ar_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
return;
}
- // XXX: we really want the default database ID...
- $storeWiki = $storeWiki ?: wfWikiID();
+ $storeWiki = $storeWiki ?: $this->loadBalancer->getLocalDomainID();
+ // @FIXME: when would getDomainID() be false here?
$dbWiki = $dbWiki ?: wfWikiID();
if ( $dbWiki === $storeWiki ) {
if ( !isset( $infos[$tableName] ) ) {
throw new \InvalidArgumentException( "Invalid table name \$tableName" );
}
- if ( $wiki === wfWikiID() ) {
+ if ( $wiki === $this->lbFactory->getLocalDomainID() ) {
$wiki = false;
}
+
if ( isset( $this->stores[$tableName][$wiki] ) ) {
return $this->stores[$tableName][$wiki];
}
$dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
$setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$tables = [ 'revision_actor_temp' ];
$field = 'revactor_actor';
$pageField = 'revactor_page';
$tsField = 'revactor_timestamp';
$joins = [];
- } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+ } else {
$tables = [ 'revision' ];
$field = 'rev_user_text';
$pageField = 'rev_page';
$tsField = 'rev_timestamp';
$joins = [];
- } else {
- $tables = [ 'revision', 'revision_actor_temp', 'actor' ];
- $field = 'COALESCE( actor_name, rev_user_text)';
- $pageField = 'rev_page';
- $tsField = 'rev_timestamp';
- $joins = [
- 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ],
- 'actor' => [ 'LEFT JOIN', 'revactor_actor = actor_id' ],
- ];
}
$watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
// Avoid PHP 7.1 warning of passing $this by reference
$apiModule = $this;
- Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ] );
+ Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ], '1.25' );
$desc = self::escapeWikiText( $desc );
if ( is_array( $desc ) ) {
$desc = implode( "\n", $desc );
// Avoid PHP 7.1 warning of passing $this by reference
$apiModule = $this;
- Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ] );
+ Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ], '1.25' );
if ( !$desc ) {
$desc = [];
// Actually count the actions using a subquery (T66505 and T66507)
$tables = [ 'recentchanges' ];
$joins = [];
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_OLD ) {
$userCond = 'rc_user_text = user_name';
} else {
$tables[] = 'actor';
- $joins['actor'] = [
- $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
- 'rc_actor = actor_id'
- ];
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
- $userCond = 'actor_user = user_id';
- } else {
- $userCond = 'actor_user = user_id OR (rc_actor = 0 AND rc_user_text = user_name)';
- }
+ $joins['actor'] = [ 'JOIN', 'rc_actor = actor_id' ];
+ $userCond = 'actor_user = user_id';
}
$timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
$this->addFields( [
$result = $this->getResult();
$revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
- // For MIGRATION_NEW, target indexes on the revision_actor_temp table.
- // Otherwise, revision is fine because it'll have to check all revision rows anyway.
- $pageField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'revactor_page' : 'rev_page';
- $idField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+ // For SCHEMA_COMPAT_READ_NEW, target indexes on the
+ // revision_actor_temp table, otherwise on the revision table.
+ $pageField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
+ ? 'revactor_page' : 'rev_page';
+ $idField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
? 'revactor_actor' : $revQuery['fields']['rev_user'];
- $countField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+ $countField = ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW )
? 'revactor_actor' : $revQuery['fields']['rev_user_text'];
// First, count anons
$from = $fromName ? "$op= " . $dbSecondary->addQuotes( $fromName ) : false;
// For the new schema, pull from the actor table. For the
- // old, pull from rev_user. For migration a FULL [OUTER]
- // JOIN would be what we want, except MySQL doesn't support
- // that so we have to UNION instead.
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ // old, pull from rev_user.
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$res = $dbSecondary->select(
'actor',
[ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
$fname,
[ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
);
- } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+ } else {
$res = $dbSecondary->select(
'revision',
[ 'actor_id' => 'NULL', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
$fname,
[ 'DISTINCT', 'ORDER BY' => [ "rev_user_text $sort" ], 'LIMIT' => $limit ]
);
- } else {
- // There are three queries we have to combine to be sure of getting all results:
- // - actor table (any rows that have been migrated will have empty rev_user_text)
- // - revision+actor by user id
- // - revision+actor by name for anons
- $options = $dbSecondary->unionSupportsOrderAndLimit()
- ? [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ] : [];
- $subsql = [];
- $subsql[] = $dbSecondary->selectSQLText(
- 'actor',
- [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
- array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
- $fname,
- $options
- );
- $subsql[] = $dbSecondary->selectSQLText(
- [ 'revision', 'actor' ],
- [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
- array_merge(
- [ "rev_user_text$like", 'rev_user != 0' ],
- $from ? [ "rev_user_text $from" ] : []
- ),
- $fname,
- array_merge( [ 'DISTINCT' ], $options ),
- [ 'actor' => [ 'LEFT JOIN', 'rev_user = actor_user' ] ]
- );
- $subsql[] = $dbSecondary->selectSQLText(
- [ 'revision', 'actor' ],
- [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
- array_merge(
- [ "rev_user_text$like", 'rev_user = 0' ],
- $from ? [ "rev_user_text $from" ] : []
- ),
- $fname,
- array_merge( [ 'DISTINCT' ], $options ),
- [ 'actor' => [ 'LEFT JOIN', 'rev_user_text = actor_name' ] ]
- );
- $sql = $dbSecondary->unionQueries( $subsql, false ) . " ORDER BY user_name $sort";
- $sql = $dbSecondary->limitResult( $sql, $limit );
- $res = $dbSecondary->query( $sql, $fname );
}
$count = 0;
}
// For the new schema, just select from the actor table. For the
- // old and transitional schemas, select from user and left join
- // actor if it exists.
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ // old, select from user.
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$res = $dbSecondary->select(
'actor',
[ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
__METHOD__,
[ 'ORDER BY' => "user_id $sort" ]
);
- } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+ } else {
$res = $dbSecondary->select(
'user',
[ 'actor_id' => 'NULL', 'user_id' => 'user_id', 'user_name' => 'user_name' ],
__METHOD__,
[ 'ORDER BY' => "user_id $sort" ]
);
- } else {
- $res = $dbSecondary->select(
- [ 'user', 'actor' ],
- [ 'actor_id', 'user_id', 'user_name' ],
- array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
- __METHOD__,
- [ 'ORDER BY' => "user_id $sort" ],
- [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
- );
}
$userIter = UserArray::newFromResult( $res );
$batchSize = count( $ids );
}
// For the new schema, just select from the actor table. For the
- // old and transitional schemas, select from user and left join
- // actor if it exists then merge in any unknown users (IPs and imports).
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ // old, select from user then merge in any unknown users (IPs and imports).
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$res = $dbSecondary->select(
'actor',
[ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
);
$userIter = UserArray::newFromResult( $res );
} else {
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
- $res = $dbSecondary->select(
- 'user',
- [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
- array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
- __METHOD__
- );
- } else {
- $res = $dbSecondary->select(
- [ 'user', 'actor' ],
- [ 'actor_id', 'user_id', 'user_name' ],
- array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
- __METHOD__,
- [],
- [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
- );
- }
+ $res = $dbSecondary->select(
+ 'user',
+ [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
+ array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
+ __METHOD__
+ );
foreach ( $res as $row ) {
$names[$row->user_name] = $row;
}
$batchSize = count( $names );
}
- // During migration, force ordering on the client side because we're
- // having to combine multiple queries that would otherwise have
- // different sort orders.
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_BOTH ||
- $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_NEW
- ) {
- $batchSize = 1;
- }
-
// With the new schema, the DB query will order by actor so update $this->orderBy to match.
- if ( $batchSize > 1 && $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ if ( $batchSize > 1 && ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
$this->orderBy = 'actor';
}
$userIter->next();
}
- // Ugh. We have to run the query three times, once for each
- // possible 'orcond' from ActorMigration, and then merge them all
- // together in the proper order. And preserving the correct
- // $hookData for each one.
- // @todo When ActorMigration is removed, this can go back to a
- // single prepare and select.
- $merged = [];
- foreach ( [ 'actor', 'userid', 'username' ] as $which ) {
- if ( $this->prepareQuery( $users, $limit - $count, $which ) ) {
- $hookData = [];
- $res = $this->select( __METHOD__, [], $hookData );
- foreach ( $res as $row ) {
- $merged[] = [ $row, &$hookData ];
- }
- }
- }
- $neg = $this->params['dir'] == 'newer' ? 1 : -1;
- usort( $merged, function ( $a, $b ) use ( $neg, $batchSize ) {
- if ( $batchSize === 1 ) { // One user, can't be different
- $ret = 0;
- } elseif ( $this->orderBy === 'id' ) {
- $ret = $a[0]->rev_user <=> $b[0]->rev_user;
- } elseif ( $this->orderBy === 'name' ) {
- $ret = strcmp( $a[0]->rev_user_text, $b[0]->rev_user_text );
- } else {
- $ret = $a[0]->rev_actor <=> $b[0]->rev_actor;
- }
-
- if ( !$ret ) {
- $ret = strcmp(
- wfTimestamp( TS_MW, $a[0]->rev_timestamp ),
- wfTimestamp( TS_MW, $b[0]->rev_timestamp )
- );
- }
-
- if ( !$ret ) {
- $ret = $a[0]->rev_id <=> $b[0]->rev_id;
- }
-
- return $neg * $ret;
- } );
- $merged = array_slice( $merged, 0, $limit - $count + 1 );
- // (end "Ugh")
+ $hookData = [];
+ $this->prepareQuery( $users, $limit - $count );
+ $res = $this->select( __METHOD__, [], $hookData );
if ( $this->fld_sizediff ) {
$revIds = [];
- foreach ( $merged as $data ) {
- if ( $data[0]->rev_parent_id ) {
- $revIds[] = $data[0]->rev_parent_id;
+ foreach ( $res as $row ) {
+ if ( $row->rev_parent_id ) {
+ $revIds[] = $row->rev_parent_id;
}
}
$this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
->listRevisionSizes( $dbSecondary, $revIds );
}
- foreach ( $merged as $data ) {
- $row = $data[0];
- $hookData = &$data[1];
+ foreach ( $res as $row ) {
if ( ++$count > $limit ) {
// We've reached the one extra which shows that there are
// additional pages to be had. Stop here...
* Prepares the query and returns the limit of rows requested
* @param User[] $users
* @param int $limit
- * @param string $which 'actor', 'userid', or 'username'
- * @return bool
*/
- private function prepareQuery( array $users, $limit, $which ) {
+ private function prepareQuery( array $users, $limit ) {
global $wgActorTableSchemaMigrationStage, $wgChangeTagsSchemaMigrationStage;
$this->resetQueryParams();
$this->addJoinConds( $revQuery['joins'] );
$this->addFields( $revQuery['fields'] );
- $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
- if ( !isset( $revWhere['orconds'][$which] ) ) {
- return false;
- }
- $this->addWhere( $revWhere['orconds'][$which] );
-
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
+ $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
$orderUserField = 'rev_actor';
$userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
- } else {
- $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
- $userField = $revQuery['fields'][$orderUserField];
- }
- if ( $which === 'actor' ) {
$tsField = 'revactor_timestamp';
$idField = 'revactor_rev';
} else {
+ // If we're dealing with user names (rather than IDs) in read-old mode,
+ // pass false for ActorMigration::getWhere()'s $useId parameter so
+ // $revWhere['conds'] isn't an OR.
+ $revWhere = ActorMigration::newMigration()
+ ->getWhere( $db, 'rev_user', $users, $this->orderBy === 'id' );
+ $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
+ $userField = $revQuery['fields'][$orderUserField];
$tsField = 'rev_timestamp';
$idField = 'rev_id';
}
+ $this->addWhere( $revWhere['conds'] );
+
// Handle continue parameter
if ( !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
$this->addWhereFld( 'ct_tag', $this->params['tag'] );
}
}
-
- return true;
}
/**
$cache = [];
- # Common conditions
- $conds = [
- 'page_is_redirect' => 0,
- 'page_namespace' => NS_MEDIAWIKI,
- ];
-
- $mostused = [];
+ $mostused = []; // list of "<cased message key>/<code>"
if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
if ( !$this->cache->has( $wgLanguageCode ) ) {
$this->load( $wgLanguageCode );
}
}
+ // Get the list of software-defined messages in core/extensions
+ $overridable = array_flip( Language::getMessageKeysFor( $wgLanguageCode ) );
+
+ // Common conditions
+ $conds = [
+ 'page_is_redirect' => 0,
+ 'page_namespace' => NS_MEDIAWIKI,
+ ];
if ( count( $mostused ) ) {
$conds['page_title'] = $mostused;
} elseif ( $code !== $wgLanguageCode ) {
$dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
}
- # Conditions to fetch oversized pages to ignore them
- $bigConds = $conds;
- $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
-
- # Load titles for all oversized pages in the MediaWiki namespace
+ // Set the stubs for oversized software-defined messages in the main cache map
$res = $dbr->select(
'page',
[ 'page_title', 'page_latest' ],
- $bigConds,
+ array_merge( $conds, [ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
__METHOD__ . "($code)-big"
);
foreach ( $res as $row ) {
- $cache[$row->page_title] = '!TOO BIG';
+ $name = $this->contLang->lcfirst( $row->page_title );
+ // Include entries/stubs for all keys in $mostused in adaptive mode
+ if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+ $cache[$row->page_title] = '!TOO BIG';
+ }
// At least include revision ID so page changes are reflected in the hash
$cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
}
- # Conditions to load the remaining pages with their contents
- $smallConds = $conds;
- $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
-
+ // Set the text for small software-defined messages in the main cache map
$res = $dbr->select(
[ 'page', 'revision', 'text' ],
- [ 'page_title', 'old_id', 'old_text', 'old_flags' ],
- $smallConds,
+ [ 'page_title', 'page_latest', 'old_id', 'old_text', 'old_flags' ],
+ array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
__METHOD__ . "($code)-small",
[],
[
'text' => [ 'JOIN', 'rev_text_id=old_id' ],
]
);
-
foreach ( $res as $row ) {
- $text = Revision::getRevisionText( $row );
- if ( $text === false ) {
- // Failed to fetch data; possible ES errors?
- // Store a marker to fetch on-demand as a workaround...
- // TODO Use a differnt marker
- $entry = '!TOO BIG';
- wfDebugLog(
- 'MessageCache',
- __METHOD__
- . ": failed to load message page text for {$row->page_title} ($code)"
- );
+ $name = $this->contLang->lcfirst( $row->page_title );
+ // Include entries/stubs for all keys in $mostused in adaptive mode
+ if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+ $text = Revision::getRevisionText( $row );
+ if ( $text === false ) {
+ // Failed to fetch data; possible ES errors?
+ // Store a marker to fetch on-demand as a workaround...
+ // TODO Use a differnt marker
+ $entry = '!TOO BIG';
+ wfDebugLog(
+ 'MessageCache',
+ __METHOD__
+ . ": failed to load message page text for {$row->page_title} ($code)"
+ );
+ } else {
+ $entry = ' ' . $text;
+ }
+ $cache[$row->page_title] = $entry;
} else {
- $entry = ' ' . $text;
+ // T193271: cache object gets too big and slow to generate.
+ // At least include revision ID so page changes are reflected in the hash.
+ $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
}
- $cache[$row->page_title] = $entry;
}
$cache['VERSION'] = MSG_CACHE_VERSION;
Hooks::run( 'MessageCache::get', [ &$lckey ] );
// Loop through each language in the fallback list until we find something useful
- $lang = wfGetLangObj( $langcode );
$message = $this->getMessageFromFallbackChain(
- $lang,
+ wfGetLangObj( $langcode ),
$lckey,
!$this->mDisable && $useDB
);
$this->getMessagePageName( $langcode, $uckey ),
$langcode
);
-
if ( $message !== false ) {
return $message;
}
$this->load( $code );
$entry = $this->cache->getField( $code, $title );
+
if ( $entry !== null ) {
+ // Message page exists as an override of a software messages
if ( substr( $entry, 0, 1 ) === ' ' ) {
// The message exists and is not '!TOO BIG'
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
+ // The text might be '-' or missing due to some data loss
return false;
}
- // Fall through and try invididual message cache below
- } else {
- // Message does not have a MediaWiki page definition
- $message = false;
- Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
- if ( $message !== false ) {
- $this->cache->setField( $code, $title, ' ' . $message );
- } else {
- $this->cache->setField( $code, $title, '!NONEXISTENT' );
- }
-
- return $message;
- }
-
- if ( $this->cacheVolatile[$code] ) {
- $entry = false;
- // Make sure that individual keys respect the WAN cache holdoff period too
- LoggerFactory::getInstance( 'MessageCache' )->debug(
- __METHOD__ . ': loading volatile key \'{titleKey}\'',
- [ 'titleKey' => $title, 'code' => $code ] );
+ // Load the message page, utilizing the individual message cache.
+ // If the page does not exist, there will be no hook handler fallbacks.
+ $entry = $this->loadCachedMessagePageEntry(
+ $title,
+ $code,
+ $this->cache->getField( $code, 'HASH' )
+ );
} else {
- // Try the individual message cache
+ // Message page does not exist or does not override a software message.
+ // Load the message page, utilizing the individual message cache.
$entry = $this->loadCachedMessagePageEntry(
$title,
$code,
$this->cache->getField( $code, 'HASH' )
);
+ if ( substr( $entry, 0, 1 ) !== ' ' ) {
+ // Message does not have a MediaWiki page definition; try hook handlers
+ $message = false;
+ Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
+ if ( $message !== false ) {
+ $this->cache->setField( $code, $title, ' ' . $message );
+ } else {
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
+ }
+
+ return $message;
+ }
}
if ( $entry !== false && substr( $entry, 0, 1 ) === ' ' ) {
- $this->cache->setField( $code, $title, $entry );
+ if ( $this->cacheVolatile[$code] ) {
+ // Make sure that individual keys respect the WAN cache holdoff period too
+ LoggerFactory::getInstance( 'MessageCache' )->debug(
+ __METHOD__ . ': loading volatile key \'{titleKey}\'',
+ [ 'titleKey' => $title, 'code' => $code ] );
+ } else {
+ $this->cache->setField( $code, $title, $entry );
+ }
// The message exists, so make sure a string is returned
return (string)substr( $entry, 1 );
}
$fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
$joinConds = [];
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
$tables[] = 'actor';
$fields[] = 'actor_id';
$joinConds['actor'] = [
- $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
[ 'actor_user = user_id' ]
];
}
$this->cache[$userId]['name'] = $row->user_name;
$this->cache[$userId]['real_name'] = $row->user_real_name;
$this->cache[$userId]['registration'] = $row->user_registration;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
$this->cache[$userId]['actor'] = $row->actor_id;
}
$usersToCheck[$userId] = $row->user_name;
global $wgActorTableSchemaMigrationStage;
wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->rc_user or $row->rc_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\Blob;
use Wikimedia\Rdbms\ResultWrapper;
use Wikimedia\Rdbms\DBConnectionError;
use Wikimedia\Rdbms\DBUnexpectedError;
+use Wikimedia\Rdbms\DBExpectedError;
/**
* @ingroup Database
return false;
}
- function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
global $wgDBOracleDRCP;
+
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError(
$this,
$this->close();
$this->user = $user;
$this->password = $password;
- // changed internal variables functions
- // mServer now holds the TNS endpoint
- // mDBname is schema name if different from username
if ( !$server ) {
- // backward compatibillity (server used to be null and TNS was supplied in dbname)
+ // Backward compatibility (server used to be null and TNS was supplied in dbname)
$this->server = $dbName;
- $this->dbName = $user;
+ $realDatabase = $user;
} else {
+ // $server now holds the TNS endpoint
$this->server = $server;
- if ( !$dbName ) {
- $this->dbName = $user;
- } else {
- $this->dbName = $dbName;
- }
+ // $dbName is schema name if different from username
+ $realDatabase = $dbName ?: $user;
}
if ( !strlen( $user ) ) { # e.g. the class is being loaded
}
Wikimedia\restoreWarnings();
- if ( $this->user != $this->dbName ) {
+ if ( $this->user != $realDatabase ) {
// change current schema in session
- $this->selectDB( $this->dbName );
+ $this->selectDB( $realDatabase );
+ } else {
+ $this->currentDomain = new DatabaseDomain(
+ $realDatabase,
+ null,
+ $tablePrefix
+ );
}
if ( !$this->conn ) {
atc.table_name
) || '_' ||
atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->dbName}')
- AND atc.owner = upper('{$this->dbName}')" );
+ AND asq.sequence_owner = upper('{$this->getDBname()}')
+ AND atc.owner = upper('{$this->getDBname()}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = [
$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
}
- $owner = strtoupper( $this->dbName );
+ $owner = strtoupper( $this->getDBname() );
$result = $this->doQuery( "SELECT table_name FROM all_tables " .
"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
$table = $this->tableName( $table );
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
- $owner = strtoupper( $this->dbName );
+ $owner = strtoupper( $this->getDBname() );
$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
$res = $this->doQuery( $sql );
if ( $res ) {
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
- $owner = $this->addQuotes( strtoupper( $this->dbName ) );
+ $owner = $this->addQuotes( strtoupper( $this->getDBname() ) );
$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $sql );
if ( $res && $res->numRows() > 0 ) {
return true;
}
- function selectDB( $db ) {
- $this->dbName = $db;
- if ( $db == null || $db == $this->user ) {
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $domain->getSchema() !== null ) {
+ // We use the *database* aspect of $domain for schema, not the domain schema
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ if ( $database === null || $database === $this->user ) {
+ // Backward compatibility
+ $this->currentDomain = $domain;
+
return true;
}
- $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
+
+ // https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj32268.html
+ $encDatabase = $this->addIdentifierQuotes( strtoupper( $database ) );
+ $sql = "ALTER SESSION SET CURRENT_SCHEMA=$encDatabase";
$stmt = oci_parse( $this->conn, $sql );
Wikimedia\suppressWarnings();
$success = oci_execute( $stmt );
Wikimedia\restoreWarnings();
- if ( !$success ) {
+ if ( $success ) {
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
+ } else {
$e = oci_error( $stmt );
- if ( $e['code'] != '1435' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
- }
-
- return false;
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
return true;
// all deletions on these tables have transactions so final failure rollbacks these updates
// @todo: Normalize the schema to match MySQL, no special FKs and such
$table = $this->tableName( $table );
- if ( $table == $this->tableName( 'user' ) && $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $table == $this->tableName( 'user' ) &&
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$this->update( 'archive', [ 'ar_user' => 0 ],
[ 'ar_user' => $conds['user_id'] ], $fname );
$this->update( 'ipblocks', [ 'ipb_user' => 0 ],
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- function getDBname() {
- return $this->dbName;
- }
-
function getServer() {
return $this->server;
}
static function selectFields() {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->fa_user or $row->fa_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
'fa_minor_mime',
'fa_user',
'fa_user_text',
- 'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : 'NULL',
+ 'fa_actor' => 'NULL',
'fa_timestamp',
'fa_deleted',
'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
global $wgActorTableSchemaMigrationStage;
wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->img_user or $row->img_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
'img_minor_mime',
'img_user',
'img_user_text',
- 'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : 'NULL',
+ 'img_actor' => 'NULL',
'img_timestamp',
'img_sha1',
] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
}
}
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$fields['oi_user'] = 'img_user';
$fields['oi_user_text'] = 'img_user_text';
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$fields['oi_actor'] = 'img_actor';
}
- if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
- $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+ if (
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
) {
// Upgrade any rows that are still old-style. Otherwise an upgrade
// might be missed if a deletion happens while the migration script
}
}
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$fields['fa_user'] = 'img_user';
$fields['fa_user_text'] = 'img_user_text';
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$fields['fa_actor'] = 'img_actor';
}
- if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
- $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+ if (
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_BOTH
) {
// Upgrade any rows that are still old-style. Otherwise an upgrade
// might be missed if a deletion happens while the migration script
global $wgActorTableSchemaMigrationStage;
wfDeprecated( __METHOD__, '1.31' );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
// If code is using this instead of self::getQueryInfo(), there's a
// decent chance it's going to try to directly access
// $row->oi_user or $row->oi_user_text and we can't give it
- // useful values here once those aren't being written anymore.
+ // useful values here once those aren't being used anymore.
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
);
}
'oi_minor_mime',
'oi_user',
'oi_user_text',
- 'oi_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'oi_actor' : 'NULL',
+ 'oi_actor' => 'NULL',
'oi_timestamp',
'oi_deleted',
'oi_sha1',
public function getOOUI( $value ) {
if ( !empty( $this->mParams['rawrow'] ) ) {
if ( !( $value instanceof OOUI\FieldLayout ) ) {
- throw new Exception( "'default' must be a FieldLayout or subclass when using 'rawrow'" );
+ wfDeprecated( "'default' parameter as a string when using 'rawrow' " .
+ "(must be a FieldLayout or subclass)", '1.32' );
}
return $value;
}
/**
* @param string $siteName
* @param string|null $admin
- * @param array $option
+ * @param array $options
*/
- function __construct( $siteName, $admin = null, array $option = [] ) {
+ function __construct( $siteName, $admin = null, array $options = [] ) {
global $wgContLang;
parent::__construct();
- if ( isset( $option['scriptpath'] ) ) {
+ if ( isset( $options['scriptpath'] ) ) {
$this->specifiedScriptPath = true;
}
foreach ( $this->optionMap as $opt => $global ) {
- if ( isset( $option[$opt] ) ) {
- $GLOBALS[$global] = $option[$opt];
- $this->setVar( $global, $option[$opt] );
+ if ( isset( $options[$opt] ) ) {
+ $GLOBALS[$global] = $options[$opt];
+ $this->setVar( $global, $options[$opt] );
}
}
- if ( isset( $option['lang'] ) ) {
+ if ( isset( $options['lang'] ) ) {
global $wgLang, $wgLanguageCode;
- $this->setVar( '_UserLang', $option['lang'] );
- $wgLanguageCode = $option['lang'];
+ $this->setVar( '_UserLang', $options['lang'] );
+ $wgLanguageCode = $options['lang'];
$wgContLang = MediaWikiServices::getInstance()->getContentLanguage();
- $wgLang = Language::factory( $option['lang'] );
+ $wgLang = Language::factory( $options['lang'] );
RequestContext::getMain()->setLanguage( $wgLang );
}
$this->setVar( '_AdminName', $admin );
}
- if ( !isset( $option['installdbuser'] ) ) {
+ if ( !isset( $options['installdbuser'] ) ) {
$this->setVar( '_InstallUser',
$this->getVar( 'wgDBuser' ) );
$this->setVar( '_InstallPassword',
$this->getVar( 'wgDBpassword' ) );
} else {
$this->setVar( '_InstallUser',
- $option['installdbuser'] );
+ $options['installdbuser'] );
$this->setVar( '_InstallPassword',
- $option['installdbpass'] ?? "" );
+ $options['installdbpass'] ?? "" );
// Assume that if we're given the installer user, we'll create the account.
$this->setVar( '_CreateDBAccount', true );
}
- if ( isset( $option['pass'] ) ) {
- $this->setVar( '_AdminPassword', $option['pass'] );
+ if ( isset( $options['pass'] ) ) {
+ $this->setVar( '_AdminPassword', $options['pass'] );
}
// Detect and inject any extension found
- if ( isset( $option['with-extensions'] ) ) {
+ if ( isset( $options['extensions'] ) ) {
+ $status = $this->validateExtensions(
+ 'extension', 'extensions', $options['extensions'] );
+ if ( !$status->isOK() ) {
+ $this->showStatusMessage( $status );
+ }
+ $this->setVar( '_Extensions', $status->value );
+ } elseif ( isset( $options['with-extensions'] ) ) {
$this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
}
// Set up the default skins
- $skins = array_keys( $this->findExtensions( 'skins' ) );
+ if ( isset( $options['skins'] ) ) {
+ $status = $this->validateExtensions( 'skin', 'skins', $options['skins'] );
+ if ( !$status->isOK() ) {
+ $this->showStatusMessage( $status );
+ }
+ $skins = $status->value;
+ } else {
+ $skins = array_keys( $this->findExtensions( 'skins' ) );
+ }
$this->setVar( '_Skins', $skins );
if ( $skins ) {
}
}
+ private function validateExtensions( $type, $directory, $nameLists ) {
+ $extensions = [];
+ $status = new Status;
+ foreach ( (array)$nameLists as $nameList ) {
+ foreach ( explode( ',', $nameList ) as $name ) {
+ $name = trim( $name );
+ if ( $name === '' ) {
+ continue;
+ }
+ $extStatus = $this->getExtensionInfo( $type, $directory, $name );
+ if ( $extStatus->isOK() ) {
+ $extensions[] = $name;
+ } else {
+ $status->merge( $extStatus );
+ }
+ }
+ }
+ $extensions = array_unique( $extensions );
+ $status->value = $extensions;
+ return $status;
+ }
+
/**
* Main entry point.
*/
*/
protected function migrateActors() {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW &&
+ if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
!$this->updateRowExists( 'MigrateActors' )
) {
$this->output(
}
/**
- * Finds extensions that follow the format /$directory/Name/Name.php,
- * and returns an array containing the value for 'Name' for each found extension.
+ * Find extensions or skins in a subdirectory of $IP.
+ * Returns an array containing the value for 'Name' for each found extension.
*
- * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
- *
- * @param string $directory Directory to search in
+ * @param string $directory Directory to search in, relative to $IP, must be either "extensions"
+ * or "skins"
* @return array [ $extName => [ 'screenshots' => [ '...' ] ]
*/
public function findExtensions( $directory = 'extensions' ) {
+ switch ( $directory ) {
+ case 'extensions':
+ return $this->findExtensionsByType( 'extension', 'extensions' );
+ case 'skins':
+ return $this->findExtensionsByType( 'skin', 'skins' );
+ default:
+ throw new InvalidArgumentException( "Invalid extension type" );
+ }
+ }
+
+ /**
+ * Find extensions or skins, and return an array containing the value for 'Name' for each found
+ * extension.
+ *
+ * @param string $type Either "extension" or "skin"
+ * @param string $directory Directory to search in, relative to $IP
+ * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
+ */
+ protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
if ( $this->getVar( 'IP' ) === null ) {
return [];
}
return [];
}
- // extensions -> extension.json, skins -> skin.json
- $jsonFile = substr( $directory, 0, strlen( $directory ) - 1 ) . '.json';
-
$dh = opendir( $extDir );
$exts = [];
while ( ( $file = readdir( $dh ) ) !== false ) {
if ( !is_dir( "$extDir/$file" ) ) {
continue;
}
- $fullJsonFile = "$extDir/$file/$jsonFile";
- $isJson = file_exists( $fullJsonFile );
- $isPhp = false;
- if ( !$isJson ) {
- // Only fallback to PHP file if JSON doesn't exist
- $fullPhpFile = "$extDir/$file/$file.php";
- $isPhp = file_exists( $fullPhpFile );
- }
- if ( $isJson || $isPhp ) {
- // Extension exists. Now see if there are screenshots
- $exts[$file] = [];
- if ( is_dir( "$extDir/$file/screenshots" ) ) {
- $paths = glob( "$extDir/$file/screenshots/*.png" );
- foreach ( $paths as $path ) {
- $exts[$file]['screenshots'][] = str_replace( $extDir, "../$directory", $path );
- }
-
- }
- }
- if ( $isJson ) {
- $info = $this->readExtension( $fullJsonFile );
- if ( $info === false ) {
- continue;
- }
- $exts[$file] += $info;
+ $status = $this->getExtensionInfo( $type, $directory, $file );
+ if ( $status->isOK() ) {
+ $exts[$file] = $status->value;
}
}
closedir( $dh );
return $exts;
}
+ /**
+ * @param string $type Either "extension" or "skin"
+ * @param string $parentRelPath The parent directory relative to $IP
+ * @param string $name The extension or skin name
+ * @return Status An object containing an error list. If there were no errors, an associative
+ * array of information about the extension can be found in $status->value.
+ */
+ protected function getExtensionInfo( $type, $parentRelPath, $name ) {
+ if ( $this->getVar( 'IP' ) === null ) {
+ throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
+ }
+ if ( $type !== 'extension' && $type !== 'skin' ) {
+ throw new InvalidArgumentException( "Invalid extension type" );
+ }
+ $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
+ $relDir = "../$parentRelPath/$name";
+ if ( !is_dir( $absDir ) ) {
+ return Status::newFatal( 'config-extension-not-found', $name );
+ }
+ $jsonFile = $type . '.json';
+ $fullJsonFile = "$absDir/$jsonFile";
+ $isJson = file_exists( $fullJsonFile );
+ $isPhp = false;
+ if ( !$isJson ) {
+ // Only fallback to PHP file if JSON doesn't exist
+ $fullPhpFile = "$absDir/$name.php";
+ $isPhp = file_exists( $fullPhpFile );
+ }
+ if ( !$isJson && !$isPhp ) {
+ return Status::newFatal( 'config-extension-not-found', $name );
+ }
+
+ // Extension exists. Now see if there are screenshots
+ $info = [];
+ if ( is_dir( "$absDir/screenshots" ) ) {
+ $paths = glob( "$absDir/screenshots/*.png" );
+ foreach ( $paths as $path ) {
+ $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
+ }
+ }
+
+ if ( $isJson ) {
+ $jsonStatus = $this->readExtension( $fullJsonFile );
+ if ( !$jsonStatus->isOK() ) {
+ return $jsonStatus;
+ }
+ $info += $jsonStatus->value;
+ }
+
+ return Status::newGood( $info );
+ }
+
/**
* @param string $fullJsonFile
* @param array $extDeps
* @param array $skinDeps
*
- * @return array|bool False if this extension can't be loaded
+ * @return Status On success, an array of extension information is in $status->value. On
+ * failure, the Status object will have an error list.
*/
private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
$load = [
foreach ( $extDeps as $dep ) {
$fname = "$extDir/$dep/extension.json";
if ( !file_exists( $fname ) ) {
- return false;
+ return Status::newFatal( 'config-extension-not-found', $dep );
}
$load[$fname] = 1;
}
foreach ( $skinDeps as $dep ) {
$fname = "$skinDir/$dep/skin.json";
if ( !file_exists( $fname ) ) {
- return false;
+ return Status::newFatal( 'config-extension-not-found', $dep );
}
$load[$fname] = 1;
}
) {
// If something is incompatible with a dependency, we have no real
// option besides skipping it
- return false;
+ return Status::newFatal( 'config-extension-dependency',
+ basename( dirname( $fullJsonFile ) ), $e->getMessage() );
} elseif ( $e->missingExtensions || $e->missingSkins ) {
// There's an extension missing in the dependency tree,
// so add those to the dependency list and try again
);
}
// Some other kind of dependency error?
- return false;
+ return Status::newFatal( 'config-extension-dependency',
+ basename( dirname( $fullJsonFile ) ), $e->getMessage() );
}
$ret = [];
// The order of credits will be the order of $load,
}
$ret['type'] = $credits['type'];
- return $ret;
+ return Status::newGood( $ret );
}
/**
"config-skins-screenshot": "$1 ($2)",
"config-extensions-requires": "$1 (requires $2)",
"config-screenshot": "screenshot",
+ "config-extension-not-found": "Could not find the registration file for the extension \"$1\"",
+ "config-extension-dependency": "A dependency error was encountered while installing the extension \"$1\": $2",
"mainpagetext": "<strong>MediaWiki has been installed.</strong>",
"mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
}
"config-skins-screenshot": "Radio button text, $1 is the skin name, and $2 is a link to a screenshot of that skin, where the link text is {{msg-mw|config-screenshot}}.",
"config-extensions-requires": "Radio button text, $1 is the extension name, and $2 are links to other extensions that this one requires.\n{{Identical|Require}}",
"config-screenshot": "Link text for the link in {{msg-mw|config-skins-screenshot}}\n{{Identical|Screenshot}}",
+ "config-extension-not-found": "An error shown when an extension or skin named by the user could not be found.\n* $1 is the extension name",
+ "config-extension-dependency": "An error shown if an extension could not be loaded due to it depending on the wrong version of MediaWiki or an uninstallable extension.\n* $1 is the extension name\n* $2 is a more detailed explanation, in English",
"mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
"mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
}
}
return false;
}
+
+ /**
+ * Whether to retry the job.
+ * @return bool
+ */
+ public function allowRetries() {
+ // ThumbnailRenderJob is a warmup for the thumbnails cache,
+ // so loosing it is not a problem. Most times the job fails
+ // for non-renderable or missing images which will not be fixed
+ // by a retry, but will create additional load on the renderer.
+ return false;
+ }
}
throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
}
+ public function selectDomain( $domain ) {
+ // Disallow things that might confuse the LoadBalancer tracking
+ throw new DBUnexpectedError( $this, "Database selection is disallowed to enable reuse." );
+ }
+
public function getDBname() {
return $this->__call( __FUNCTION__, func_get_args() );
}
protected $user;
/** @var string Password used to establish the current connection */
protected $password;
- /** @var string Database that this instance is currently connected to */
- protected $dbName;
/** @var array[] Map of (table => (dbname, schema, prefix) map) */
protected $tableAliases = [];
/** @var string[] Map of (index alias => index) */
/** @var bool Whether to suppress triggering of transaction end callbacks */
protected $trxEndCallbacksSuppressed = false;
- /** @var string */
- protected $tablePrefix = '';
- /** @var string */
- protected $schema = '';
/** @var int */
protected $flags;
/** @var array */
* @param array $params Parameters passed from Database::factory()
*/
protected function __construct( array $params ) {
- foreach ( [ 'host', 'user', 'password', 'dbname' ] as $name ) {
+ foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
$this->connectionParams[$name] = $params[$name];
}
- $this->schema = $params['schema'];
- $this->tablePrefix = $params['tablePrefix'];
-
$this->cliMode = $params['cliMode'];
// Agent name is added to SQL queries in a comment, so make sure it can't break out
$this->agent = str_replace( '/', '-', $params['agent'] );
}
// Set initial dummy domain until open() sets the final DB/prefix
- $this->currentDomain = DatabaseDomain::newUnspecified();
+ $this->currentDomain = new DatabaseDomain(
+ $params['dbname'] != '' ? $params['dbname'] : null,
+ $params['schema'] != '' ? $params['schema'] : null,
+ $params['tablePrefix']
+ );
}
/**
}
// Establish the connection
$this->doInitConnection();
- // Set the domain object after open() sets the relevant fields
- if ( $this->dbName != '' ) {
- // Domains with server scope but a table prefix are not used by IDatabase classes
- $this->currentDomain = new DatabaseDomain( $this->dbName, null, $this->tablePrefix );
- }
}
/**
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['password'],
- $this->connectionParams['dbname']
+ $this->connectionParams['dbname'],
+ $this->connectionParams['schema'],
+ $this->connectionParams['tablePrefix']
);
} else {
throw new InvalidArgumentException( "No database user provided." );
* @param string $user Database user name
* @param string $password Database user password
* @param string $dbName Database name
+ * @param string|null $schema Database schema name
+ * @param string $tablePrefix Table prefix
* @return bool
* @throws DBConnectionError
*/
- abstract protected function open( $server, $user, $password, $dbName );
+ abstract protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix );
/**
* Construct a Database subclass instance given a database type and parameters
$p['flags'] = $p['flags'] ?? 0;
$p['variables'] = $p['variables'] ?? [];
$p['tablePrefix'] = $p['tablePrefix'] ?? '';
- $p['schema'] = $p['schema'] ?? '';
+ $p['schema'] = $p['schema'] ?? null;
$p['cliMode'] = $p['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
$p['agent'] = $p['agent'] ?? '';
if ( !isset( $p['connLogger'] ) ) {
}
public function tablePrefix( $prefix = null ) {
- $old = $this->tablePrefix;
+ $old = $this->currentDomain->getTablePrefix();
if ( $prefix !== null ) {
- $this->tablePrefix = $prefix;
- $this->currentDomain = ( $this->dbName != '' )
- ? new DatabaseDomain( $this->dbName, null, $this->tablePrefix )
- : DatabaseDomain::newUnspecified();
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ $this->currentDomain->getSchema(),
+ $prefix
+ );
}
return $old;
}
public function dbSchema( $schema = null ) {
- $old = $this->schema;
+ $old = $this->currentDomain->getSchema();
if ( $schema !== null ) {
- $this->schema = $schema;
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ // DatabaseDomain uses null for unspecified schemas
+ strlen( $schema ) ? $schema : null,
+ $this->currentDomain->getTablePrefix()
+ );
}
- return $old;
+ return (string)$old;
+ }
+
+ /**
+ * @return string Schema to use to qualify relations in queries
+ */
+ protected function relationSchemaQualifier() {
+ return $this->dbSchema();
}
public function getLBInfo( $name = null ) {
return array_merge(
[
'db_server' => $this->server,
- 'db_name' => $this->dbName,
+ 'db_name' => $this->getDBname(),
'db_user' => $this->user,
],
$extras
return false;
}
- public function selectDB( $db ) {
- # Stub. Shouldn't cause serious problems if it's not overridden, but
- # if your database engine supports a concept similar to MySQL's
- # databases you may as well.
- $this->dbName = $db;
+ final public function selectDB( $db ) {
+ $this->selectDomain( new DatabaseDomain(
+ $db,
+ $this->currentDomain->getSchema(),
+ $this->currentDomain->getTablePrefix()
+ ) );
return true;
}
+ final public function selectDomain( $domain ) {
+ $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
+ }
+
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ $this->currentDomain = $domain;
+ }
+
public function getDBname() {
- return $this->dbName;
+ return $this->currentDomain->getDatabase();
}
public function getServer() {
$database = $this->tableAliases[$table]['dbname'];
$schema = is_string( $this->tableAliases[$table]['schema'] )
? $this->tableAliases[$table]['schema']
- : $this->schema;
+ : $this->relationSchemaQualifier();
$prefix = is_string( $this->tableAliases[$table]['prefix'] )
? $this->tableAliases[$table]['prefix']
- : $this->tablePrefix;
+ : $this->tablePrefix();
} else {
$database = '';
- $schema = $this->schema; # Default schema
- $prefix = $this->tablePrefix; # Default prefix
+ $schema = $this->relationSchemaQualifier(); # Default schema
+ $prefix = $this->tablePrefix(); # Default prefix
}
}
$this->opened = false;
$this->conn = false;
try {
- $this->open( $this->server, $this->user, $this->password, $this->dbName );
+ $this->open(
+ $this->server,
+ $this->user,
+ $this->password,
+ $this->getDBname(),
+ $this->dbSchema(),
+ $this->tablePrefix()
+ );
$this->lastPing = microtime( true );
$ok = true;
$this->conn = false;
$this->trxEndCallbacks = []; // don't copy
$this->handleSessionLoss(); // no trx or locks anymore
- $this->open( $this->server, $this->user, $this->password, $this->dbName );
+ $this->open(
+ $this->server,
+ $this->user,
+ $this->password,
+ $this->getDBname(),
+ $this->dbSchema(),
+ $this->tablePrefix()
+ );
$this->lastPing = microtime( true );
}
}
parent::__construct( $params );
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
throw new DBConnectionError(
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$connectionInfo = [];
- if ( $dbName ) {
+ if ( $dbName != '' ) {
$connectionInfo['Database'] = $dbName;
}
}
$this->opened = true;
+ $this->currentDomain = new DatabaseDomain(
+ ( $dbName != '' ) ? $dbName : null,
+ null,
+ $tablePrefix
+ );
return (bool)$this->conn;
}
}
if ( $schema === false ) {
- $schema = $this->schema;
+ $schema = $this->dbSchema();
}
$res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
$s );
}
- /**
- * @param string $db
- * @return bool
- */
- public function selectDB( $db ) {
- try {
- $this->dbName = $db;
- $this->query( "USE $db" );
- return true;
- } catch ( Exception $e ) {
- return false;
- }
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ $encDatabase = $this->addIdentifierQuotes( $domain->getDatabase() );
+ $this->query( "USE $encDatabase" );
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
+
+ return true;
}
/**
private function populateColumnCaches() {
$res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
[
- 'TABLE_CATALOG' => $this->dbName,
- 'TABLE_SCHEMA' => $this->schema,
+ 'TABLE_CATALOG' => $this->getDBname(),
+ 'TABLE_SCHEMA' => $this->dbSchema(),
'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
] );
return 'mysql';
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Close/unset connection handle
$this->close();
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$this->installErrorHandler();
try {
- $this->conn = $this->mysqlConnect( $this->server );
+ $this->conn = $this->mysqlConnect( $this->server, $dbName );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
}
if ( strlen( $dbName ) ) {
- Wikimedia\suppressWarnings();
- $success = $this->selectDB( $dbName );
- Wikimedia\restoreWarnings();
- if ( !$success ) {
- $error = $this->lastError();
- $this->queryLogger->error(
- "Error selecting database {db_name} on server {db_server}: {error}",
- $this->getLogContext( [
- 'method' => __METHOD__,
- 'error' => $error,
- ] )
- );
- throw new DBConnectionError( $this, "Error selecting database $dbName: $error" );
- }
+ $this->selectDomain( new DatabaseDomain( $dbName, null, $tablePrefix ) );
+ } else {
+ $this->currentDomain = new DatabaseDomain( null, null, $tablePrefix );
}
// Tell the server what we're communicating with
* Open a connection to a MySQL server
*
* @param string $realServer
+ * @param string|null $dbName
* @return mixed Raw connection
* @throws DBConnectionError
*/
- abstract protected function mysqlConnect( $realServer );
+ abstract protected function mysqlConnect( $realServer, $dbName );
/**
* Set the character set of the MySQL link
*/
public function listViews( $prefix = null, $fname = __METHOD__ ) {
// The name of the column containing the name of the VIEW
- $propertyName = 'Tables_in_' . $this->dbName;
+ $propertyName = 'Tables_in_' . $this->getDBname();
// Query for the VIEWS
$res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
/**
* @param string $realServer
+ * @param string|null $dbName
* @return bool|mysqli
* @throws DBConnectionError
*/
- protected function mysqlConnect( $realServer ) {
+ protected function mysqlConnect( $realServer, $dbName ) {
# Avoid suppressed fatal error, which is very hard to track down
if ( !function_exists( 'mysqli_init' ) ) {
throw new DBConnectionError( $this, "MySQLi functions missing,"
}
$mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
- if ( $mysqli->real_connect( $realServer, $this->user,
- $this->password, $this->dbName, $port, $socket, $connFlags )
- ) {
+ if ( $mysqli->real_connect(
+ $realServer,
+ $this->user,
+ $this->password,
+ $dbName,
+ $port,
+ $socket,
+ $connFlags
+ ) ) {
return $mysqli;
}
return $conn->affected_rows;
}
- /**
- * @param string $db
- * @return bool
- */
- function selectDB( $db ) {
+ function doSelectDomain( DatabaseDomain $domain ) {
$conn = $this->getBindingHandle();
- $this->dbName = $db;
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
+ }
+
+ $database = $domain->getDatabase();
+ if ( !$conn->select_db( $database ) ) {
+ throw new DBExpectedError( $this, "Could not select database '$database'." );
+ }
+
+ // Update that domain fields on success (no exception thrown)
+ $this->currentDomain = $domain;
- return $conn->select_db( $db );
+ return true;
}
/**
return false;
}
- protected function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
throw new DBConnectionError(
$this->server = $server;
$this->user = $user;
$this->password = $password;
- $this->dbName = $dbName;
$connectVars = [
// pg_connect() user $user as the default database. Since a database is *required*,
$this->query( "SET standard_conforming_strings = on", __METHOD__ );
$this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
- $this->determineCoreSchema( $this->schema );
- // The schema to be used is now in the search path; no need for explicit qualification
- $this->schema = '';
+ $this->determineCoreSchema( $schema );
+ $this->currentDomain = new DatabaseDomain( $dbName, $schema, $tablePrefix );
- return $this->conn;
+ return (bool)$this->conn;
+ }
+
+ protected function relationSchemaQualifier() {
+ if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
+ // The schema to be used is now in the search path; no need for explicit qualification
+ return '';
+ }
+
+ return parent::relationSchemaQualifier();
}
public function databasesAreIndependent() {
return true;
}
- /**
- * Postgres doesn't support selectDB in the same way MySQL does. So if the
- * DB name doesn't match the open connection, open a new one
- * @param string $db
- * @return bool
- * @throws DBUnexpectedError
- */
- public function selectDB( $db ) {
- if ( $this->dbName !== $db ) {
- return (bool)$this->open( $this->server, $this->user, $this->password, $db );
+ public function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $this->getDBname() !== $domain->getDatabase() ) {
+ // Postgres doesn't support selectDB in the same way MySQL does.
+ // So if the DB name doesn't match the open connection, open a new one
+ $this->open(
+ $this->server,
+ $this->user,
+ $this->password,
+ $domain->getDatabase(),
+ $domain->getSchema(),
+ $domain->getTablePrefix()
+ );
} else {
- return true;
+ $this->currentDomain = $domain;
}
+
+ return true;
}
/**
return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
- public function getDBname() {
- return $this->dbName;
- }
-
public function getServer() {
return $this->server;
}
if ( isset( $p['dbFilePath'] ) ) {
$this->dbPath = $p['dbFilePath'];
$lockDomain = md5( $this->dbPath );
+ // Use "X" for things like X.sqlite and ":memory:" for RAM-only DBs
+ if ( !isset( $p['dbname'] ) || !strlen( $p['dbname'] ) ) {
+ $p['dbname'] = preg_replace( '/\.sqlite\d?$/', '', basename( $this->dbPath ) );
+ }
} elseif ( isset( $p['dbDirectory'] ) ) {
$this->dbDir = $p['dbDirectory'];
$lockDomain = $p['dbname'];
*/
public static function newStandaloneInstance( $filename, array $p = [] ) {
$p['dbFilePath'] = $filename;
- $p['schema'] = false;
+ $p['schema'] = null;
$p['tablePrefix'] = '';
/** @var DatabaseSqlite $db */
$db = Database::factory( 'sqlite', $p );
protected function doInitConnection() {
if ( $this->dbPath !== null ) {
// Standalone .sqlite file mode.
- $this->openFile( $this->dbPath, $this->connectionParams['dbname'] );
+ $this->openFile(
+ $this->dbPath,
+ $this->connectionParams['dbname'],
+ $this->connectionParams['tablePrefix']
+ );
} elseif ( $this->dbDir !== null ) {
// Stock wiki mode using standard file names per DB
if ( strlen( $this->connectionParams['dbname'] ) ) {
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['password'],
- $this->connectionParams['dbname']
+ $this->connectionParams['dbname'],
+ $this->connectionParams['schema'],
+ $this->connectionParams['tablePrefix']
);
} else {
// Caller will manually call open() later?
return false;
}
- protected function open( $server, $user, $pass, $dbName ) {
+ protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
$this->close();
$fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
// Only $dbName is used, the other parameters are irrelevant for SQLite databases
- $this->openFile( $fileName, $dbName );
+ $this->openFile( $fileName, $dbName, $tablePrefix );
return (bool)$this->conn;
}
*
* @param string $fileName
* @param string $dbName
+ * @param string $tablePrefix
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
- protected function openFile( $fileName, $dbName ) {
+ protected function openFile( $fileName, $dbName, $tablePrefix ) {
$err = false;
$this->dbPath = $fileName;
$this->opened = is_object( $this->conn );
if ( $this->opened ) {
- $this->dbName = $dbName;
+ $this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
# Set error codes only, don't raise exceptions
$this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
/**
* Get/set the table prefix.
* @param string|null $prefix The table prefix to set, or omitted to leave it unchanged.
- * @return string The previous table prefix.
+ * @return string The previous table prefix
+ * @throws DBUnexpectedError
*/
public function tablePrefix( $prefix = null );
/**
* Get/set the db schema.
* @param string|null $schema The database schema to set, or omitted to leave it unchanged.
- * @return string The previous db schema.
+ * @return string The previous db schema
*/
public function dbSchema( $schema = null );
public function getFlag( $flag );
/**
+ * Return the currently selected domain ID
+ *
+ * Null components (database/schema) might change once a connection is established
+ *
* @return string
*/
public function getDomainID();
* Change the current database
*
* @param string $db
- * @return bool Success or failure
+ * @return bool True unless an exception was thrown
* @throws DBConnectionError If databasesAreIndependent() is true and an error occurs
+ * @throws DBError
+ * @deprecated Since 1.32
*/
public function selectDB( $db );
+ /**
+ * Set the current domain (database, schema, and table prefix)
+ *
+ * This will throw an error for some database types if the database unspecified
+ *
+ * @param string|DatabaseDomain $domain
+ * @since 1.32
+ * @throws DBConnectionError
+ */
+ public function selectDomain( $domain );
+
/**
* Get the current DB name
- * @return string
+ * @return string|null
*/
public function getDBname();
* @param int $i Server index
* @param string $domain Domain ID to open
* @param int $flags Class CONN_* constant bitfield
- * @return Database
+ * @return Database|bool Returns false on connection error
+ * @throws DBError When database selection fails
*/
private function openForeignConnection( $i, $domain, $flags = 0 ) {
$domainInstance = DatabaseDomain::newFromId( $domain );
- $dbName = $domainInstance->getDatabase();
- $prefix = $domainInstance->getTablePrefix();
$autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
if ( $autoCommit ) {
$connInUseKey = self::KEY_FOREIGN_INUSE;
}
+ /** @var Database $conn */
if ( isset( $this->conns[$connInUseKey][$i][$domain] ) ) {
// Reuse an in-use connection for the same domain
$conn = $this->conns[$connInUseKey][$i][$domain];
// Reuse a free connection from another domain
$conn = reset( $this->conns[$connFreeKey][$i] );
$oldDomain = key( $this->conns[$connFreeKey][$i] );
- if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
- $this->lastError = "Error selecting database '$dbName' on server " .
- $conn->getServer() . " from client host {$this->hostname}";
- $this->errorConnection = $conn;
- $conn = false;
+ if ( $domainInstance->getDatabase() !== null ) {
+ $conn->selectDomain( $domainInstance );
} else {
- $conn->tablePrefix( $prefix );
- unset( $this->conns[$connFreeKey][$i][$oldDomain] );
- // Note that if $domain is an empty string, getDomainID() might not match it
- $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
- $this->connLogger->debug( __METHOD__ .
- ": reusing free connection from $oldDomain for $domain" );
+ // Stay on the current database, but update the schema/prefix
+ $conn->dbSchema( $domainInstance->getSchema() );
+ $conn->tablePrefix( $domainInstance->getTablePrefix() );
}
+ unset( $this->conns[$connFreeKey][$i][$oldDomain] );
+ // Note that if $domain is an empty string, getDomainID() might not match it
+ $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
+ $this->connLogger->debug( __METHOD__ .
+ ": reusing free connection from $oldDomain for $domain" );
} else {
if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No server with index '$i'." );
) );
}
- protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+ protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
return $this->db;
}
}
$relations = $this->relations;
// Ensure actor relations are set
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH &&
+ if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) &&
empty( $relations['target_author_actor'] )
) {
$actorIds = [];
$params['authorActors'] = $actorIds;
}
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW ) {
+ if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
unset( $relations['target_author_id'], $relations['target_author_ip'] );
unset( $params['authorIds'], $params['authorIPs'] );
}
// rd_fragment and rd_interwiki were added later, populate them if empty
if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
+ // (T203942) We can't redirect to Media namespace because it's virtual.
+ // We don't want to modify Title objects farther down the
+ // line. So, let's fix this here by changing to File namespace.
+ if ( $row->rd_namespace == NS_MEDIA ) {
+ $namespace = NS_FILE;
+ } else {
+ $namespace = $row->rd_namespace;
+ }
$this->mRedirectTarget = Title::makeTitle(
- $row->rd_namespace, $row->rd_title,
+ $namespace, $row->rd_title,
$row->rd_fragment, $row->rd_interwiki
);
return $this->mRedirectTarget;
if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
$dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
}
);
}
- // TODO remove this method once old parser cache objects have expired, probably mid-October 2018
- public function __wakeup() {
- // T203716 remove wrapper that was added by logic in an older version of this class,
- // where the wrapper was included in mText. This might sometimes remove a wrapper that's
- // genuine content (manually added to a system message), but that will work out OK, see below.
- $text = $this->getRawText();
- $start = Html::openElement( 'div', [
- 'class' => 'mw-parser-output'
- ] );
- $startLen = strlen( $start );
- $end = Html::closeElement( 'div' );
- $endPos = strrpos( $text, $end );
- $endLen = strlen( $end );
- if ( substr( $text, 0, $startLen ) === $start && $endPos !== false
- // if the closing div is followed by real content, bail out of unwrapping
- && preg_match( '/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
- ) {
- $text = substr( $text, $startLen );
- $text = substr( $text, 0, $endPos - $startLen ) .
- substr( $text, $endPos - $startLen + $endLen );
- $this->setText( $text );
- // We found a wrapper to remove, so the ParserOutput was probably created by the
- // code path that now contains an addWrapperDivClass( 'mw-parser-output' ) call,
- // but it did not contain it when this object was cached, so we need to fix the
- // wrapper class variable.
- // If this was a message with a manually added wrapper, we are technically wrong about
- // this but we were wrong about the unwrapping as well so it will work out just right,
- // except when this is a normal page view of such a message page, in which case
- // it will be single-wrapped instead of double-wrapped (harmless) or something wants
- // render the message with unwrap=true (in which case the message won't be wrapped even
- // though it should, but the few code paths using unwrap=true only do it for real pages).
- $this->clearWrapperDivClass();
- $this->addWrapperDivClass( 'mw-parser-output' );
- }
- }
-
/**
* Merges internal metadata such as flags, accessed options, and profiling info
* from $source into this ParserOutput. This should be used whenever the state of $source
$virtualOldBits |= $removedBits;
$status->successCount++;
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
if ( $item->getAuthorId() > 0 ) {
$authorIds[] = $item->getAuthorId();
} elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
$authorIPs[] = $item->getAuthorName();
}
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$authorActors[] = $item->getAuthorActor();
}
// Log it
$authorFields = [];
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$authorFields['authorIds'] = $authorIds;
$authorFields['authorIPs'] = $authorIPs;
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$authorFields['authorActors'] = $authorActors;
}
$this->updateLog(
$userTitle = Title::makeTitleSafe( NS_USER, $name );
$userDbKey = $userTitle->getDBkey();
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
# Hide name from live edits
$dbw->update(
'revision',
);
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
if ( $actorId ) {
# Hide name from live edits
$offenderName = $opts->getValue( 'offender' );
$offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
if ( $offender ) {
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
+ } elseif ( $offender->getId() > 0 ) {
+ $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
} else {
- if ( $offender->getId() > 0 ) {
- $field = 'target_author_id';
- $value = $offender->getId();
- } else {
- $field = 'target_author_ip';
- $value = $offender->getName();
- }
- if ( !$offender->getActorId() ) {
- $qc = [ 'ls_field' => $field, 'ls_value' => $value ];
- } else {
- $db = wfGetDB( DB_REPLICA );
- $qc = [
- 'ls_field' => [ 'target_author_actor', $field ], // So LogPager::getQueryInfo() works right
- $db->makeList( [
- $db->makeList(
- [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ], LIST_AND
- ),
- $db->makeList( [ 'ls_field' => $field, 'ls_value' => $value ], LIST_AND ),
- ], LIST_OR ),
- ];
- }
+ $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
}
}
} else {
$conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
$queryInfo['conds'][] = $conds['conds'];
// Force the appropriate index to avoid bad query plans (T189026)
- if ( count( $conds['orconds'] ) === 1 ) {
- if ( isset( $conds['orconds']['actor'] ) ) {
- // @todo: This will need changing when revision_comment_temp goes away
- $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
- } else {
- $queryInfo['options']['USE INDEX']['revision'] =
- isset( $conds['orconds']['userid'] ) ? 'user_timestamp' : 'usertext_timestamp';
- }
+ if ( isset( $conds['orconds']['actor'] ) ) {
+ // @todo: This will need changing when revision_comment_temp goes away
+ $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
+ } else {
+ $queryInfo['options']['USE INDEX']['revision'] =
+ isset( $conds['orconds']['userid'] ) ? 'user_timestamp' : 'usertext_timestamp';
}
}
}
$conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
$conds['rc_namespace'] = NS_FILE;
- if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
$jcond = 'rc_actor = ' . $imgQuery['fields']['img_actor'];
} else {
$rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
public static function newFromActorId( $id ) {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+ // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) ) {
throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage is MIGRATION_OLD'
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_NEW'
);
}
$user = new User;
$user->mFrom = 'defaults';
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD && $actorId !== null ) {
+ // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) && $actorId !== null ) {
$user->mActorId = (int)$actorId;
if ( $user->mActorId !== 0 ) {
$user->mFrom = 'actor';
$this->mGroupMemberships = null; // deferred
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
if ( isset( $row->actor_id ) ) {
$this->mActorId = (int)$row->actor_id;
if ( $this->mActorId !== 0 ) {
public function getActorId( IDatabase $dbw = null ) {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+ // Technically we should always return 0 without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
return 0;
}
// Currently $this->mActorId might be null if $this was loaded from a
// cache entry that was written when $wgActorTableSchemaMigrationStage
- // was MIGRATION_OLD. Once that is no longer a possibility (i.e. when
+ // was SCHEMA_COMPAT_OLD. Once that is no longer a possibility (i.e. when
// User::VERSION is incremented after $wgActorTableSchemaMigrationStage
// has been removed), that condition may be removed.
if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
);
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->update(
'actor',
[ 'actor_name' => $this->mName ],
$dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
if ( $dbw->affectedRows() ) {
$newUser = self::newFromId( $dbw->insertId() );
+ $newUser->mName = $fields['user_name'];
+ $newUser->updateActorId( $dbw );
// Load the user from master to avoid replica lag
$newUser->load( self::READ_LATEST );
- $newUser->updateActorId( $dbw );
} else {
$newUser = null;
}
private function updateActorId( IDatabase $dbw ) {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->insert(
'actor',
[ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
],
'joins' => [],
];
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+
+ // Technically we shouldn't allow this without SCHEMA_COMPAT_READ_NEW,
+ // but it does little harm and might be needed for write callers loading a User.
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_NEW ) {
$ret['tables']['user_actor'] = 'actor';
$ret['fields'][] = 'user_actor.actor_id';
$ret['joins']['user_actor'] = [
- $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ? 'JOIN' : 'LEFT JOIN',
[ 'user_actor.actor_user = user_id' ]
];
}
+
return $ret;
}
}
public function execute() {
- global $wgActorTableSchemaMigrationStage;
-
$dbw = $this->getDB( DB_MASTER );
// Autodetect mode...
$actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
- $needSpecialQuery = ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
- $wgActorTableSchemaMigrationStage !== MIGRATION_NEW );
- if ( $needSpecialQuery ) {
- foreach ( $actorQuery['joins'] as &$j ) {
- $j[0] = 'JOIN'; // replace LEFT JOIN
- }
- unset( $j );
- }
-
if ( $backgroundMode ) {
$this->output( "Using replication-friendly background mode...\n" );
for ( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
$max = $min + $chunkSize;
- if ( $needSpecialQuery ) {
- // Use separate subqueries to collect counts with the old
- // and new schemas, to avoid having to do whole-table scans.
- $result = $dbr->select(
- [
- 'user',
- 'rev1' => '('
- . $dbr->selectSQLText(
- [ 'revision', 'revision_actor_temp' ],
- [ 'rev_user', 'ct' => 'COUNT(*)' ],
- [
- "rev_user > $min AND rev_user <= $max",
- 'revactor_rev' => null,
- ],
- __METHOD__,
- [ 'GROUP BY' => 'rev_user' ],
- [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
- ) . ')',
- 'rev2' => '('
- . $dbr->selectSQLText(
- [ 'revision' ] + $actorQuery['tables'],
- [ 'actor_user', 'ct' => 'COUNT(*)' ],
- "actor_user > $min AND actor_user <= $max",
- __METHOD__,
- [ 'GROUP BY' => 'actor_user' ],
- $actorQuery['joins']
- ) . ')',
- ],
- [ 'user_id', 'user_editcount' => 'COALESCE(rev1.ct,0) + COALESCE(rev2.ct,0)' ],
- "user_id > $min AND user_id <= $max",
- __METHOD__,
- [],
- [
- 'rev1' => [ 'LEFT JOIN', 'user_id = rev_user' ],
- 'rev2' => [ 'LEFT JOIN', 'user_id = actor_user' ],
- ]
- );
- } else {
- $revUser = $actorQuery['fields']['rev_user'];
- $result = $dbr->select(
- [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
- [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
- "user_id > $min AND user_id <= $max",
- __METHOD__,
- [ 'GROUP BY' => 'user_id' ],
- [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
- );
- }
+ $revUser = $actorQuery['fields']['rev_user'];
+ $result = $dbr->select(
+ [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
+ [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
+ "user_id > $min AND user_id <= $max",
+ __METHOD__,
+ [ 'GROUP BY' => 'user_id' ],
+ [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
+ );
foreach ( $result as $row ) {
$dbw->update( 'user',
$this->output( "Using single-query mode...\n" );
$user = $dbw->tableName( 'user' );
- if ( $needSpecialQuery ) {
- $subquery1 = $dbw->selectSQLText(
- [ 'revision', 'revision_actor_temp' ],
- [ 'COUNT(*)' ],
- [
- 'user_id = rev_user',
- 'revactor_rev' => null,
- ],
- __METHOD__,
- [],
- [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
- );
- $subquery2 = $dbw->selectSQLText(
- [ 'revision' ] + $actorQuery['tables'],
- [ 'COUNT(*)' ],
- 'user_id = actor_user',
- __METHOD__,
- [],
- $actorQuery['joins']
- );
- $dbw->query(
- "UPDATE $user SET user_editcount=($subquery1) + ($subquery2)",
- __METHOD__
- );
- } else {
- $subquery = $dbw->selectSQLText(
- [ 'revision' ] + $actorQuery['tables'],
- [ 'COUNT(*)' ],
- [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
- __METHOD__,
- [],
- $actorQuery['joins']
- );
- $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
- }
+ $subquery = $dbw->selectSQLText(
+ [ 'revision' ] + $actorQuery['tables'],
+ [ 'COUNT(*)' ],
+ [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
+ __METHOD__,
+ [],
+ $actorQuery['joins']
+ );
+ $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
}
$this->output( "Done!\n" );
$this->addOption( 'env-checks', "Run environment checks only, don't change anything" );
$this->addOption( 'with-extensions', "Detect and include extensions" );
+ $this->addOption( 'extensions', 'Comma-separated list of extensions to install',
+ false, true, false, true );
+ $this->addOption( 'skins', 'Comma-separated list of skins to install (default: all)',
+ false, true, false, true );
}
public function getDbType() {
protected function doDBUpdates() {
global $wgActorTableSchemaMigrationStage;
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW ) {
+ if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) ) {
$this->output(
- "...cannot update while \$wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
+ "...cannot update while \$wgActorTableSchemaMigrationStage lacks SCHEMA_COMPAT_WRITE_NEW\n"
);
return false;
}
$table,
[
$actorField => $row->actor_id,
- $nameField => '',
],
array_intersect_key( (array)$row, $pkFilter ) + [
$actorField => 0
}
$this->beginTransaction( $dbw, __METHOD__ );
$dbw->insert( $newTable, $inserts, __METHOD__ );
- $dbw->update( $table, [ $nameField => '' ], [ $primaryKey => $updates ], __METHOD__ );
$countUpdated += $dbw->affectedRows();
$this->commitTransaction( $dbw, __METHOD__ );
}
$tables = [ self::$tableMap[$prefix] ];
$fields = [];
$joins = [];
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
$fields['userid'] = $prefix . '_user';
$fields['username'] = $prefix . '_user_text';
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ // Read the new fields if we're writing them regardless of read mode, to handle upgrades
if ( $prefix === 'rev' ) {
$tables[] = 'revision_actor_temp';
$joins['revision_actor_temp'] = [
- $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+ ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ? 'LEFT JOIN' : 'JOIN',
'rev_id = revactor_rev',
];
$fields['actorid'] = 'revactor_actor';
$log->addRelations( 'log_id', $items, $row->log_id );
// Query item author relations...
$fields = [];
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
$fields['userid'] = 'log_user';
$fields['username'] = 'log_user_text';
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
+ // Read the new fields if we're writing them regardless of read mode, to handle upgrades
$fields['actorid'] = 'log_actor';
}
// Add item author relations...
$userIds = $userIPs = $userActors = [];
foreach ( $sres as $srow ) {
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
if ( $srow->userid > 0 ) {
$userIds[] = intval( $srow->userid );
} elseif ( $srow->username != '' ) {
$userIPs[] = $srow->username;
}
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
if ( $srow->actorid ) {
$userActors[] = intval( $srow->actorid );
} elseif ( $srow->userid > 0 ) {
}
}
// Add item author relations...
- if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$log->addRelations( 'target_author_id', $userIds, $row->log_id );
$log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$log->addRelations( 'target_author_actor', $userActors, $row->log_id );
}
}
if ( $total ) {
# Reassign edits
$this->output( "\nReassigning current edits..." );
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$dbw->update(
'revision',
[
'rev_user' => $to->getId(),
- 'rev_user_text' =>
- $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $to->getName() : ''
+ 'rev_user_text' => $to->getName(),
],
$from->isLoggedIn()
? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
__METHOD__
);
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->update(
'revision_actor_temp',
[ 'revactor_actor' => $to->getActorId( $dbw ) ],
}
/**
- * Return user specifications
+ * Return user specifications for an UPDATE
* i.e. user => id, user_text => text
*
* @param IDatabase $dbw Database handle
global $wgActorTableSchemaMigrationStage;
$ret = [];
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$ret += [
$idfield => $user->getId(),
- $utfield => $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $user->getName() : '',
+ $utfield => $user->getName(),
];
}
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$ret += [ $acfield => $user->getActorId( $dbw ) ];
}
return $ret;
$delUser = [];
$delActor = [];
$dbr = $this->getDB( DB_REPLICA );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$res = $dbr->select(
[ 'user', 'actor' ],
[ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
$this->output( "\nDeleting unused accounts..." );
$dbw = $this->getDB( DB_MASTER );
$dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
# Keep actor rows referenced from ipblocks
$keep = $dbw->selectFieldValues(
'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
$dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
$dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
- if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
$dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
$dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
}
- if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
$dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
}
$titles = [];
$actorQuery = ActorMigration::newMigration()
->getWhere( $dbr, 'rev_user', User::newFromName( $user, false ) );
- foreach ( $actorQuery['orconds'] as $cond ) {
- $results = $dbr->select(
- [ 'page', 'revision' ] + $actorQuery['tables'],
- [ 'page_namespace', 'page_title' ],
- [ $cond ],
- __METHOD__,
- [],
- [ 'revision' => [ 'JOIN', 'page_latest = rev_id' ] ] + $actorQuery['joins']
- );
- foreach ( $results as $row ) {
- $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
- }
+ $results = $dbr->select(
+ [ 'page', 'revision' ] + $actorQuery['tables'],
+ [ 'page_namespace', 'page_title' ],
+ $actorQuery['conds'],
+ __METHOD__,
+ [],
+ [ 'revision' => [ 'JOIN', 'page_latest = rev_id' ] ] + $actorQuery['joins']
+ );
+ foreach ( $results as $row ) {
+ $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
}
return $titles;
}
function execute() {
- global $wgVersion, $wgLang, $wgAllowSchemaUpdates;
+ global $wgVersion, $wgLang, $wgAllowSchemaUpdates, $wgMessagesDirs;
if ( !$wgAllowSchemaUpdates
&& !( $this->hasOption( 'force' )
}
}
+ // T206765: We need to load the installer i18n files as some of errors come installer/updater code
+ $wgMessagesDirs['MediawikiInstaller'] = dirname( __DIR__ ) . '/includes/installer/i18n';
+
$lang = Language::factory( 'en' );
// Set global language to ensure localised errors are in English (T22633)
RequestContext::getMain()->setLanguage( $lang );
$tables[] = 'image_comment_temp';
}
- if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
// The new tables for actors are in use
$tables[] = 'actor';
$tables[] = 'revision_actor_temp';
*/
private $mwGlobalsToUnset = [];
+ /**
+ * Holds original values of ini settings to be restored
+ * in tearDown().
+ * @see setIniSettings()
+ * @var array
+ */
+ private $iniSettings = [];
+
/**
* Holds original loggers which have been replaced by setLogger()
* @var LoggerInterface[]
foreach ( $this->mwGlobalsToUnset as $value ) {
unset( $GLOBALS[$value] );
}
+ foreach ( $this->iniSettings as $name => $value ) {
+ ini_set( $name, $value );
+ }
if (
array_key_exists( 'wgExtraNamespaces', $this->mwGlobals ) ||
in_array( 'wgExtraNamespaces', $this->mwGlobalsToUnset )
}
}
+ /**
+ * Set an ini setting for the duration of the test
+ * @param string $name Name of the setting
+ * @param string $value Value to set
+ * @since 1.32
+ */
+ protected function setIniSetting( $name, $value ) {
+ $original = ini_get( $name );
+ $this->iniSettings[$name] = $original;
+ ini_set( $name, $value );
+ }
+
/**
* Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
* Otherwise old namespace data will lurk and cause bugs.
return new ActorMigration( $stage );
}
+ /**
+ * @dataProvider provideConstructor
+ * @param int $stage
+ * @param string|null $exceptionMsg
+ */
+ public function testConstructor( $stage, $exceptionMsg ) {
+ try {
+ $m = new ActorMigration( $stage );
+ if ( $exceptionMsg !== null ) {
+ $this->fail( 'Expected exception not thrown' );
+ }
+ $this->assertInstanceOf( ActorMigration::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,
+ 'Cannot read the new schema without also writing it'
+ ],
+ [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
+
+ [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ],
+ [
+ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD,
+ 'Cannot read the old schema without also writing it'
+ ],
+ [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ],
+ [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, 'Cannot read both schemas' ],
+
+ [ 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, 'Cannot read both schemas' ],
+ ];
+ }
+
/**
* @dataProvider provideGetJoin
* @param int $stage
public static function provideGetJoin() {
return [
'Simple table, old' => [
- MIGRATION_OLD, 'rc_user', [
+ SCHEMA_COMPAT_OLD, 'rc_user', [
'tables' => [],
'fields' => [
'rc_user' => 'rc_user',
'joins' => [],
],
],
- 'Simple table, write-both' => [
- MIGRATION_WRITE_BOTH, 'rc_user', [
- 'tables' => [ 'actor_rc_user' => 'actor' ],
+ 'Simple table, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', [
+ 'tables' => [],
'fields' => [
- 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
- 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
- 'rc_actor' => 'rc_actor',
- ],
- 'joins' => [
- 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ 'rc_user' => 'rc_user',
+ 'rc_user_text' => 'rc_user_text',
+ 'rc_actor' => 'NULL',
],
+ 'joins' => [],
],
],
- 'Simple table, write-new' => [
- MIGRATION_WRITE_NEW, 'rc_user', [
+ 'Simple table, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', [
'tables' => [ 'actor_rc_user' => 'actor' ],
'fields' => [
- 'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
- 'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+ 'rc_user' => 'actor_rc_user.actor_user',
+ 'rc_user_text' => 'actor_rc_user.actor_name',
'rc_actor' => 'rc_actor',
],
'joins' => [
- 'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+ 'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
],
],
],
'Simple table, new' => [
- MIGRATION_NEW, 'rc_user', [
+ SCHEMA_COMPAT_NEW, 'rc_user', [
'tables' => [ 'actor_rc_user' => 'actor' ],
'fields' => [
'rc_user' => 'actor_rc_user.actor_user',
],
'ipblocks, old' => [
- MIGRATION_OLD, 'ipb_by', [
+ SCHEMA_COMPAT_OLD, 'ipb_by', [
'tables' => [],
'fields' => [
'ipb_by' => 'ipb_by',
'joins' => [],
],
],
- 'ipblocks, write-both' => [
- MIGRATION_WRITE_BOTH, 'ipb_by', [
- 'tables' => [ 'actor_ipb_by' => 'actor' ],
+ 'ipblocks, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', [
+ 'tables' => [],
'fields' => [
- 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
- 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
- 'ipb_by_actor' => 'ipb_by_actor',
- ],
- 'joins' => [
- 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ 'ipb_by' => 'ipb_by',
+ 'ipb_by_text' => 'ipb_by_text',
+ 'ipb_by_actor' => 'NULL',
],
+ 'joins' => [],
],
],
- 'ipblocks, write-new' => [
- MIGRATION_WRITE_NEW, 'ipb_by', [
+ 'ipblocks, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', [
'tables' => [ 'actor_ipb_by' => 'actor' ],
'fields' => [
- 'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
- 'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+ 'ipb_by' => 'actor_ipb_by.actor_user',
+ 'ipb_by_text' => 'actor_ipb_by.actor_name',
'ipb_by_actor' => 'ipb_by_actor',
],
'joins' => [
- 'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+ 'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
],
],
],
'ipblocks, new' => [
- MIGRATION_NEW, 'ipb_by', [
+ SCHEMA_COMPAT_NEW, 'ipb_by', [
'tables' => [ 'actor_ipb_by' => 'actor' ],
'fields' => [
'ipb_by' => 'actor_ipb_by.actor_user',
],
'Revision, old' => [
- MIGRATION_OLD, 'rev_user', [
+ SCHEMA_COMPAT_OLD, 'rev_user', [
'tables' => [],
'fields' => [
'rev_user' => 'rev_user',
'joins' => [],
],
],
- 'Revision, write-both' => [
- MIGRATION_WRITE_BOTH, 'rev_user', [
- 'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
- 'actor_rev_user' => 'actor',
- ],
+ 'Revision, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', [
+ 'tables' => [],
'fields' => [
- 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
- 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
- 'rev_actor' => 'temp_rev_user.revactor_actor',
- ],
- 'joins' => [
- 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
- 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ 'rev_user' => 'rev_user',
+ 'rev_user_text' => 'rev_user_text',
+ 'rev_actor' => 'NULL',
],
+ 'joins' => [],
],
],
- 'Revision, write-new' => [
- MIGRATION_WRITE_NEW, 'rev_user', [
+ 'Revision, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', [
'tables' => [
'temp_rev_user' => 'revision_actor_temp',
'actor_rev_user' => 'actor',
],
'fields' => [
- 'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
- 'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+ 'rev_user' => 'actor_rev_user.actor_user',
+ 'rev_user_text' => 'actor_rev_user.actor_name',
'rev_actor' => 'temp_rev_user.revactor_actor',
],
'joins' => [
- 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
- 'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
],
],
],
'Revision, new' => [
- MIGRATION_NEW, 'rev_user', [
+ SCHEMA_COMPAT_NEW, 'rev_user', [
'tables' => [
'temp_rev_user' => 'revision_actor_temp',
'actor_rev_user' => 'actor',
return [
'Simple table, old' => [
- MIGRATION_OLD, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_OLD, 'rc_user', $genericUser, true, [
'tables' => [],
'orconds' => [ 'userid' => "rc_user = '1'" ],
'joins' => [],
],
],
- 'Simple table, write-both' => [
- MIGRATION_WRITE_BOTH, 'rc_user', $genericUser, true, [
+ 'Simple table, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [
- 'actor' => "rc_actor = '11'",
- 'userid' => "rc_actor = '0' AND rc_user = '1'"
- ],
+ 'orconds' => [ 'userid' => "rc_user = '1'" ],
'joins' => [],
],
],
- 'Simple table, write-new' => [
- MIGRATION_WRITE_NEW, 'rc_user', $genericUser, true, [
+ 'Simple table, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $genericUser, true, [
'tables' => [],
- 'orconds' => [
- 'actor' => "rc_actor = '11'",
- 'userid' => "rc_actor = '0' AND rc_user = '1'"
- ],
+ 'orconds' => [ 'actor' => "rc_actor = '11'" ],
'joins' => [],
],
],
'Simple table, new' => [
- MIGRATION_NEW, 'rc_user', $genericUser, true, [
+ SCHEMA_COMPAT_NEW, 'rc_user', $genericUser, true, [
'tables' => [],
'orconds' => [ 'actor' => "rc_actor = '11'" ],
'joins' => [],
],
'ipblocks, old' => [
- MIGRATION_OLD, 'ipb_by', $genericUser, true, [
+ SCHEMA_COMPAT_OLD, 'ipb_by', $genericUser, true, [
'tables' => [],
'orconds' => [ 'userid' => "ipb_by = '1'" ],
'joins' => [],
],
],
- 'ipblocks, write-both' => [
- MIGRATION_WRITE_BOTH, 'ipb_by', $genericUser, true, [
+ 'ipblocks, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_by', $genericUser, true, [
'tables' => [],
- 'orconds' => [
- 'actor' => "ipb_by_actor = '11'",
- 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
- ],
+ 'orconds' => [ 'userid' => "ipb_by = '1'" ],
'joins' => [],
],
],
- 'ipblocks, write-new' => [
- MIGRATION_WRITE_NEW, 'ipb_by', $genericUser, true, [
+ 'ipblocks, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_by', $genericUser, true, [
'tables' => [],
- 'orconds' => [
- 'actor' => "ipb_by_actor = '11'",
- 'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
- ],
+ 'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
'joins' => [],
],
],
'ipblocks, new' => [
- MIGRATION_NEW, 'ipb_by', $genericUser, true, [
+ SCHEMA_COMPAT_NEW, 'ipb_by', $genericUser, true, [
'tables' => [],
'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
'joins' => [],
],
'Revision, old' => [
- MIGRATION_OLD, 'rev_user', $genericUser, true, [
+ SCHEMA_COMPAT_OLD, 'rev_user', $genericUser, true, [
'tables' => [],
'orconds' => [ 'userid' => "rev_user = '1'" ],
'joins' => [],
],
],
- 'Revision, write-both' => [
- MIGRATION_WRITE_BOTH, 'rev_user', $genericUser, true, [
- 'tables' => [
- 'temp_rev_user' => 'revision_actor_temp',
- ],
- 'orconds' => [
- 'actor' =>
- "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
- 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
- ],
- 'joins' => [
- 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
- ],
+ 'Revision, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_user', $genericUser, true, [
+ 'tables' => [],
+ 'orconds' => [ 'userid' => "rev_user = '1'" ],
+ 'joins' => [],
],
],
- 'Revision, write-new' => [
- MIGRATION_WRITE_NEW, 'rev_user', $genericUser, true, [
+ 'Revision, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_user', $genericUser, true, [
'tables' => [
'temp_rev_user' => 'revision_actor_temp',
],
- 'orconds' => [
- 'actor' =>
- "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
- 'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
- ],
+ 'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
'joins' => [
- 'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+ 'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
],
],
],
'Revision, new' => [
- MIGRATION_NEW, 'rev_user', $genericUser, true, [
+ SCHEMA_COMPAT_NEW, 'rev_user', $genericUser, true, [
'tables' => [
'temp_rev_user' => 'revision_actor_temp',
],
],
'Multiple users, old' => [
- MIGRATION_OLD, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, true, [
'tables' => [],
'orconds' => [
'userid' => "rc_user IN ('1','2','3') ",
'joins' => [],
],
],
- 'Multiple users, write-both' => [
- MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, true, [
+ 'Multiple users, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, true, [
'tables' => [],
'orconds' => [
- 'actor' => "rc_actor IN ('11','12','34') ",
- 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
- 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+ 'userid' => "rc_user IN ('1','2','3') ",
+ 'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
- 'Multiple users, write-new' => [
- MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, true, [
+ 'Multiple users, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, true, [
'tables' => [],
- 'orconds' => [
- 'actor' => "rc_actor IN ('11','12','34') ",
- 'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
- 'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
- ],
+ 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
'joins' => [],
],
],
'Multiple users, new' => [
- MIGRATION_NEW, 'rc_user', $complicatedUsers, true, [
+ SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, true, [
'tables' => [],
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
'joins' => [],
],
'Multiple users, no use ID, old' => [
- MIGRATION_OLD, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_OLD, 'rc_user', $complicatedUsers, false, [
'tables' => [],
'orconds' => [
'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
'joins' => [],
],
],
- 'Multiple users, write-both' => [
- MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, false, [
+ 'Multiple users, read-old' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rc_user', $complicatedUsers, false, [
'tables' => [],
'orconds' => [
- 'actor' => "rc_actor IN ('11','12','34') ",
- 'username' => "rc_actor = '0' AND "
- . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+ 'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
],
'joins' => [],
],
],
- 'Multiple users, write-new' => [
- MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, false, [
+ 'Multiple users, read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rc_user', $complicatedUsers, false, [
'tables' => [],
- 'orconds' => [
- 'actor' => "rc_actor IN ('11','12','34') ",
- 'username' => "rc_actor = '0' AND "
- . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
- ],
+ 'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
'joins' => [],
],
],
'Multiple users, new' => [
- MIGRATION_NEW, 'rc_user', $complicatedUsers, false, [
+ SCHEMA_COMPAT_NEW, 'rc_user', $complicatedUsers, false, [
'tables' => [],
'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
'joins' => [],
$user->method( 'getActorId' )->willReturn( $this->db->insertId() );
}
+ $stageNames = [
+ SCHEMA_COMPAT_OLD => 'old',
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => 'write-both-read-old',
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => 'write-both-read-new',
+ SCHEMA_COMPAT_NEW => 'new',
+ ];
+
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
- 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_OLD => [
+ SCHEMA_COMPAT_OLD,
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+ ],
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [
+ SCHEMA_COMPAT_OLD,
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+ SCHEMA_COMPAT_NEW
+ ],
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [
+ SCHEMA_COMPAT_OLD,
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+ SCHEMA_COMPAT_NEW
+ ],
+ SCHEMA_COMPAT_NEW => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+ SCHEMA_COMPAT_NEW
+ ],
];
$nameKey = $key . '_text';
foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_by' ) {
- $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+ $extraFields['ipb_address'] = __CLASS__ . "#{$stageNames[$writeStage]}";
}
$w = $this->makeMigration( $writeStage );
$fields = $w->getInsertValues( $this->db, $key, $user );
}
- if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
- $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
- $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
+ if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ $this->assertSame( $user->getId(), $fields[$key],
+ "old field, stage={$stageNames[$writeStage]}" );
+ $this->assertSame( $user->getName(), $fields[$nameKey],
+ "old field, stage={$stageNames[$writeStage]}" );
} else {
- $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
- $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
+ $this->assertArrayNotHasKey( $key, $fields, "old field, stage={$stageNames[$writeStage]}" );
+ $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage={$stageNames[$writeStage]}" );
}
- if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
- $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
+ if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) {
+ $this->assertSame( $user->getActorId(), $fields[$actorKey],
+ "new field, stage={$stageNames[$writeStage]}" );
} else {
- $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
+ $this->assertArrayNotHasKey( $actorKey, $fields,
+ "new field, stage={$stageNames[$writeStage]}" );
}
$this->db->insert( $table, $extraFields + $fields, __METHOD__ );
$queryInfo['joins']
);
- $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
- $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
+ $this->assertSame( $user->getId(), (int)$row->$key,
+ "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, id" );
+ $this->assertSame( $user->getName(), $row->$nameKey,
+ "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, name" );
$this->assertSame(
- $readStage === MIGRATION_OLD || $writeStage === MIGRATION_OLD ? 0 : $user->getActorId(),
+ ( $readStage & SCHEMA_COMPAT_READ_OLD ) ? 0 : $user->getActorId(),
(int)$row->$actorKey,
- "w=$writeStage, r=$readStage, actor"
+ "w={$stageNames[$writeStage]}, r={$stageNames[$readStage]}, actor"
);
}
}
public static function provideStages() {
return [
- 'MIGRATION_OLD' => [ MIGRATION_OLD ],
- 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
- 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
- 'MIGRATION_NEW' => [ MIGRATION_NEW ],
+ 'old' => [ SCHEMA_COMPAT_OLD ],
+ 'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+ 'read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
+ 'new' => [ SCHEMA_COMPAT_NEW ],
];
}
}
public function testInsertUserIdentity() {
+ $this->setMwGlobals( [
+ // for User::getActorId()
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ ] );
+ $this->overrideMwServices();
+
$user = $this->getTestUser()->getUser();
$userIdentity = $this->getMock( UserIdentity::class );
$userIdentity->method( 'getId' )->willReturn( $user->getId() );
list( $cFields, $cCallback ) = MediaWikiServices::getInstance()->getCommentStore()
->insertWithTempTable( $this->db, 'rev_comment', '' );
- $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+ $m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
list( $fields, $callback ) =
$m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
$extraFields = [
$callback( $id, $extraFields );
$cCallback( $id );
- $qi = Revision::getQueryInfo();
+ $qi = $m->getJoin( 'rev_user' );
$row = $this->db->selectRow(
- $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+ [ 'revision' ] + $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
);
$this->assertSame( $user->getId(), (int)$row->rev_user );
$this->assertSame( $user->getName(), $row->rev_user_text );
$this->assertSame( $user->getActorId(), (int)$row->rev_actor );
- $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+ $m = $this->makeMigration( SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
$fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
$this->assertSame( $user->getId(), $fields['dummy_user'] );
$this->assertSame( $user->getName(), $fields['dummy_user_text'] );
$this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
}
- public function testConstructor() {
+ public function testNewMigration() {
$m = ActorMigration::newMigration();
$this->assertInstanceOf( ActorMigration::class, $m );
$this->assertSame( $m, ActorMigration::newMigration() );
public static function provideIsAnon() {
return [
- 'MIGRATION_OLD' => [ MIGRATION_OLD, 'foo = 0', 'foo != 0' ],
- 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH, 'foo = 0', 'foo != 0' ],
- 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW, 'foo = 0', 'foo != 0' ],
- 'MIGRATION_NEW' => [ MIGRATION_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
+ 'old' => [ SCHEMA_COMPAT_OLD, 'foo = 0', 'foo != 0' ],
+ 'read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'foo = 0', 'foo != 0' ],
+ 'read-new' => [
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'foo IS NULL', 'foo IS NOT NULL'
+ ],
+ 'new' => [ SCHEMA_COMPAT_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
];
}
}
protected function tearDown() {
+ Language::factory( 'en' )->resetNamespaces();
+
if ( $this->restoreWarnings ) {
$this->restoreWarnings = false;
Wikimedia\restoreWarnings();
}
+
parent::tearDown();
}
];
}
- protected function getCompatActorQueryFields( $prefix, $tmp = false ) {
- return [
- "{$prefix}_user" => "COALESCE( actor_{$prefix}_user.actor_user, {$prefix}_user )",
- "{$prefix}_user_text" => "COALESCE( actor_{$prefix}_user.actor_name, {$prefix}_user_text )",
- "{$prefix}_actor" => $tmp ?: "{$prefix}_actor",
- ];
- }
-
- protected function getCompatActorJoins( $prefix ) {
+ protected function getNewActorJoins( $prefix ) {
return [
"temp_{$prefix}_user" => [
- "LEFT JOIN",
+ "JOIN",
"temp_{$prefix}_user.revactor_{$prefix} = {$prefix}_id",
],
"actor_{$prefix}_user" => [
- "LEFT JOIN",
+ "JOIN",
"actor_{$prefix}_user.actor_id = temp_{$prefix}_user.revactor_actor",
],
];
[
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_NEW,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[
'tables' => [
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[
'tables' => [
],
'fields' => array_merge(
$this->getArchiveQueryFields( false ),
- $this->getCompatActorQueryFields( 'ar' ),
+ $this->getNewActorQueryFields( 'ar' ),
$this->getCompatCommentQueryFields( 'ar' )
),
'joins' => [
'comment_ar_comment'
=> [ 'LEFT JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
- 'actor_ar_user' => [ 'LEFT JOIN', 'actor_ar_user.actor_id = ar_actor' ],
+ 'actor_ar_user' => [ 'JOIN', 'actor_ar_user.actor_id = ar_actor' ],
],
]
];
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[
'tables' => [
'archive',
- 'actor_ar_user' => 'actor',
'comment_ar_comment' => 'comment',
],
'fields' => array_merge(
$this->getArchiveQueryFields( true ),
$this->getContentHandlerQueryFields( 'ar' ),
- $this->getCompatActorQueryFields( 'ar' ),
+ $this->getOldActorQueryFields( 'ar' ),
$this->getCompatCommentQueryFields( 'ar' )
),
'joins' => [
'comment_ar_comment'
=> [ 'LEFT JOIN', 'comment_ar_comment.comment_id = ar_comment_id' ],
- 'actor_ar_user' => [ 'LEFT JOIN', 'actor_ar_user.actor_id = ar_actor' ],
],
]
];
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[
'tables' => [
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_NEW,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
],
[ 'page', 'user' ],
[
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
],
[ 'page', 'user' ],
[
$this->getRevisionQueryFields( false ),
$this->getPageQueryFields(),
$this->getUserQueryFields(),
- $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getCompatCommentQueryFields( 'rev' )
),
'joins' => array_merge(
'user' => [
'LEFT JOIN',
[
- 'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
- 'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user',
]
],
],
- $this->getCompatActorJoins( 'rev' ),
+ $this->getNewActorJoins( 'rev' ),
$this->getCompatCommentJoins( 'rev' )
),
]
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_NEW,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
],
[ 'page', 'user' ],
[
$this->getRevisionQueryFields( false ),
$this->getPageQueryFields(),
$this->getUserQueryFields(),
- $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getNewActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getCompatCommentQueryFields( 'rev' )
),
'joins' => array_merge(
'user' => [
'LEFT JOIN',
[
- 'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
- 'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+ 'actor_rev_user.actor_user != 0',
+ 'user_id = actor_rev_user.actor_user'
]
],
],
- $this->getCompatActorJoins( 'rev' ),
+ $this->getNewActorJoins( 'rev' ),
$this->getCompatCommentJoins( 'rev' )
),
]
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[],
[
'tables' => [
'revision',
- 'temp_rev_user' => 'revision_actor_temp',
'temp_rev_comment' => 'revision_comment_temp',
- 'actor_rev_user' => 'actor',
'comment_rev_comment' => 'comment',
],
'fields' => array_merge(
$this->getRevisionQueryFields( true ),
$this->getContentHandlerQueryFields( 'rev' ),
- $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getCompatCommentQueryFields( 'rev' )
),
'joins' => array_merge(
- $this->getCompatActorJoins( 'rev' ),
$this->getCompatCommentJoins( 'rev' )
),
]
'wgMultiContentRevisionSchemaMigrationStage'
=> SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
[ 'page', 'user' ],
[
'revision',
'page',
'user',
- 'temp_rev_user' => 'revision_actor_temp',
'temp_rev_comment' => 'revision_comment_temp',
- 'actor_rev_user' => 'actor',
'comment_rev_comment' => 'comment',
],
'fields' => array_merge(
$this->getContentHandlerQueryFields( 'rev' ),
$this->getUserQueryFields(),
$this->getPageQueryFields(),
- $this->getCompatActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
+ $this->getOldActorQueryFields( 'rev', 'temp_rev_user.revactor_actor' ),
$this->getCompatCommentQueryFields( 'rev' )
),
'joins' => array_merge(
'user' => [
'LEFT JOIN',
[
- 'COALESCE( actor_rev_user.actor_user, rev_user ) != 0',
- 'user_id = COALESCE( actor_rev_user.actor_user, rev_user )'
+ 'rev_user != 0',
+ 'user_id = rev_user'
]
],
],
- $this->getCompatActorJoins( 'rev' ),
$this->getCompatCommentJoins( 'rev' )
),
]
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[],
[
'wgContentHandlerUseDB' => true,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'page', 'user' ],
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[],
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'page' ],
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'user' ],
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'text' ],
[
'wgContentHandlerUseDB' => false,
'wgMultiContentRevisionSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
[ 'text', 'page', 'user' ],
[
[
'wgContentHandlerUseDB' => true,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
'fields' => array_merge(
[
[
'wgContentHandlerUseDB' => false,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
'fields' => array_merge(
[
[
'wgContentHandlerUseDB' => true,
'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
],
'fields' => array_merge(
[
[
'wgContentHandlerUseDB' => false,
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
],
'fields' => array_merge(
[
*/
public function testRevisionUserJoinCond() {
$this->hideDeprecated( 'Revision::userJoinCond' );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
$this->overrideMwServices();
$this->assertEquals(
[ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
use Title;
use WANObjectCache;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\DatabaseSqlite;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\LoadBalancer;
'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
] );
$this->overrideMwServices();
* @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
*/
private function getLoadBalancerMock( array $server ) {
+ $domain = new DatabaseDomain( $server['dbname'], null, $server['tablePrefix'] );
+
$lb = $this->getMockBuilder( LoadBalancer::class )
->setMethods( [ 'reallyOpenConnection' ] )
- ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
+ ->setConstructorArgs( [
+ [ 'servers' => [ $server ], 'localDomain' => $domain ]
+ ] )
->getMock();
$lb->method( 'reallyOpenConnection' )->willReturnCallback(
'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
- 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_OLD,
] );
$this->overrideMwServices();
*/
public function testLoadFromTitle() {
$this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
$this->overrideMwServices();
$title = $this->getMockTitle();
'rev_content_model' => 'GOATMODEL',
];
+ $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
$db = $this->getMock( IDatabase::class );
$db->expects( $this->any() )
->method( 'getDomainId' )
- ->will( $this->returnValue( wfWikiID() ) );
+ ->will( $this->returnValue( $domain ) );
$db->expects( $this->once() )
->method( 'selectRow' )
->with(
/**
* @return \PHPUnit_Framework_MockObject_MockObject|ILoadBalancer
*/
- private function getMockLoadBalancer() {
- return $this->getMockBuilder( ILoadBalancer::class )
+ private function getMockLoadBalancer( $localDomain ) {
+ $mock = $this->getMockBuilder( ILoadBalancer::class )
->disableOriginalConstructor()->getMock();
+
+ $mock->expects( $this->any() )
+ ->method( 'getLocalDomainID' )
+ ->willReturn( $localDomain );
+
+ return $mock;
}
/**
$mock = $this->getMockBuilder( ILBFactory::class )
->disableOriginalConstructor()->getMock();
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $localDomain = $lbFactory->getLocalDomainID();
+
+ $mock->expects( $this->any() )->method( 'getLocalDomainID' )->willReturn( $localDomain );
+
$mock->expects( $this->once() )
->method( 'getMainLB' )
->with( $this->equalTo( $expectedWiki ) )
- ->willReturnCallback( function ( $domain ) use ( $expectedWiki ) {
- return $this->getMockLoadBalancer();
+ ->willReturnCallback( function ( $domain ) use ( $localDomain ) {
+ return $this->getMockLoadBalancer( $localDomain );
} );
return $mock;
/** @dataProvider provideTestGet */
public function testGet( $tableName, $wiki, $expectedWiki ) {
$services = MediaWikiServices::getInstance();
- $db = wfGetDB( DB_MASTER );
- if ( $wiki === false ) {
- $wiki2 = $db->getWikiID();
- } else {
- $wiki2 = $wiki;
- }
+ $wiki2 = ( $wiki === false )
+ ? $services->getDBLoadBalancerFactory()->getLocalDomainID()
+ : $wiki;
$names = new NameTableStoreFactory(
$this->getMockLoadBalancerFactory( $expectedWiki ),
$services->getMainWANObjectCache(),
$wgActorTableSchemaMigrationStage = $v;
$this->overrideMwServices();
}, [ $wgActorTableSchemaMigrationStage ] );
- $wgActorTableSchemaMigrationStage = MIGRATION_WRITE_BOTH;
+ $wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD;
$this->overrideMwServices();
$users = [
/**
* @dataProvider provideSorting
- * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+ * @param int $stage SCHEMA_COMPAT contants for $wgActorTableSchemaMigrationStage
* @param array $params Extra parameters for the query
* @param bool $reverse Reverse order?
* @param int $revs Number of revisions to expect
*/
public function testSorting( $stage, $params, $reverse, $revs ) {
- if ( isset( $params['ucuserprefix'] ) &&
- ( $stage === MIGRATION_WRITE_BOTH || $stage === MIGRATION_WRITE_NEW ) &&
- $this->db->getType() === 'mysql' && $this->usesTemporaryTables()
- ) {
- // https://bugs.mysql.com/bug.php?id=10327
- $this->markTestSkipped( 'MySQL bug 10327 - can\'t reopen temporary tables' );
- }
// FIXME: fails under sqlite
$this->markTestSkippedIfDbType( 'sqlite' );
foreach (
[
- 'old' => MIGRATION_OLD,
- 'write both' => MIGRATION_WRITE_BOTH,
- 'write new' => MIGRATION_WRITE_NEW,
- 'new' => MIGRATION_NEW,
+ 'old' => SCHEMA_COMPAT_OLD,
+ 'read old' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
+ 'read new' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW,
+ 'new' => SCHEMA_COMPAT_NEW,
] as $stageName => $stage
) {
foreach ( [ false, true ] as $reverse ) {
/**
* @dataProvider provideInterwikiUser
- * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+ * @param int $stage SCHEMA_COMPAT constants for $wgActorTableSchemaMigrationStage
*/
public function testInterwikiUser( $stage ) {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
public static function provideInterwikiUser() {
return [
- 'old' => [ MIGRATION_OLD ],
- 'write both' => [ MIGRATION_WRITE_BOTH ],
- 'write new' => [ MIGRATION_WRITE_NEW ],
- 'new' => [ MIGRATION_NEW ],
+ 'old' => [ SCHEMA_COMPAT_OLD ],
+ 'read old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ],
+ 'read new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ],
+ 'new' => [ SCHEMA_COMPAT_NEW ],
];
}
wfWarn( $msg );
};
$this->currentDomain = DatabaseDomain::newUnspecified();
- $this->open( 'localhost', 'testuser', 'password', 'testdb' );
+ $this->open( 'localhost', 'testuser', 'password', 'testdb', null, '' );
}
/**
return 'test';
}
- function open( $server, $user, $password, $dbName ) {
+ function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
$this->conn = (object)[ 'test' ];
return true;
$this->assertFalse( $db->isOpen() );
} else {
\Wikimedia\suppressWarnings();
- $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ try {
+ $this->assertFalse( $db->selectDB( 'garbage-db' ) );
+ $this->fail( "No error thrown." );
+ } catch ( \Wikimedia\Rdbms\DBExpectedError $e ) {
+ $this->assertEquals(
+ "Could not select database 'garbage-db'.",
+ $e->getMessage()
+ );
+ }
\Wikimedia\restoreWarnings();
}
}
private function getMockForViews() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'fetchRow', 'query' ] )
+ ->setMethods( [ 'fetchRow', 'query', 'getDBname' ] )
->getMock();
$db->method( 'query' )
(object)[ 'Tables_in_' => 'view2' ],
(object)[ 'Tables_in_' => 'myview' ]
] ) );
+ $db->method( 'getDBname' )->willReturn( '' );
return $db;
}
public function testIndexAliases() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
->getMock();
$db->method( 'mysqlRealEscapeString' )->willReturnCallback(
function ( $s ) {
public function testTableAliases() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
- ->setMethods( [ 'mysqlRealEscapeString' ] )
+ ->setMethods( [ 'mysqlRealEscapeString', 'dbSchema', 'tablePrefix' ] )
->getMock();
$db->method( 'mysqlRealEscapeString' )->willReturnCallback(
function ( $s ) {
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle_TRX() {
- $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( '' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle_TRX() {
- $db = $this->getMockDB( [ 'isOpen', 'ping' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( 'unittest' );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
* @covers Wikimedia\Rdbms\Database::lockIsFree
*/
public function testGetScopedLock() {
- $db = $this->getMockDB( [ 'isOpen' ] );
+ $db = $this->getMockDB( [ 'isOpen', 'getDBname' ] );
$db->method( 'isOpen' )->willReturn( true );
+ $db->method( 'getDBname' )->willReturn( 'unittest' );
$this->assertEquals( 0, $db->trxLevel() );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
- public function testMutators() {
+ public function testSchemaAndPrefixMutators() {
$old = $this->db->tablePrefix();
+ $oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Prefix is string' );
- $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
- $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
- $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
+ $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
+ $this->assertSame( $old, $this->db->tablePrefix( 'xxx' ) );
+ $this->assertSame( 'xxx', $this->db->tablePrefix(), "Prefix set" );
$this->db->tablePrefix( $old );
$this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
$old = $this->db->dbSchema();
+ $oldDomain = $this->db->getDomainId();
$this->assertInternalType( 'string', $old, 'Schema is string' );
- $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
- $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
- $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
+ $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
+ $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
+ $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
$this->db->dbSchema( $old );
$this->assertNotEquals( 'xxx', $this->db->dbSchema() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::selectDomain
+ */
+ public function testSelectDomain() {
+ $oldDomain = $this->db->getDomainId();
+ $oldDatabase = $this->db->getDBname();
+ $oldSchema = $this->db->dbSchema();
+ $oldPrefix = $this->db->tablePrefix();
+
+ $this->db->selectDomain( 'testselectdb-xxx' );
+ $this->assertSame( 'testselectdb', $this->db->getDBname() );
+ $this->assertSame( '', $this->db->dbSchema() );
+ $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+ $this->db->selectDomain( $oldDomain );
+ $this->assertSame( $oldDatabase, $this->db->getDBname() );
+ $this->assertSame( $oldSchema, $this->db->dbSchema() );
+ $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
+
+ $this->db->selectDomain( 'testselectdb-schema-xxx' );
+ $this->assertSame( 'testselectdb', $this->db->getDBname() );
+ $this->assertSame( 'schema', $this->db->dbSchema() );
+ $this->assertSame( 'xxx', $this->db->tablePrefix() );
+
+ $this->db->selectDomain( $oldDomain );
+ $this->assertSame( $oldDatabase, $this->db->getDBname() );
+ $this->assertSame( $oldSchema, $this->db->dbSchema() );
+ $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
+ $this->assertSame( $oldDomain, $this->db->getDomainId() );
}
}
* @param array $selectFields
* @param string[]|null $row
* @param string[]|null $expectedFields
- * @param string $migration
+ * @param int $commentMigration
+ * @param int $actorMigration
*/
public function testNewFromId( $id,
array $selectFields,
array $row = null,
array $expectedFields = null,
- $migration
+ $commentMigration,
+ $actorMigration
) {
$this->setMwGlobals( [
- 'wgCommentTableSchemaMigrationStage' => $migration,
- 'wgActorTableSchemaMigrationStage' => $migration,
+ 'wgCommentTableSchemaMigrationStage' => $commentMigration,
+ 'wgActorTableSchemaMigrationStage' => $actorMigration,
] );
$row = $row ? (object)$row : null;
null,
null,
MIGRATION_OLD,
+ SCHEMA_COMPAT_OLD,
],
[
123,
],
[ 'type' => 'foobarize', 'comment' => 'test!' ],
MIGRATION_OLD,
+ SCHEMA_COMPAT_OLD,
],
[
567,
],
[ 'type' => 'foobarize', 'comment' => 'test!' ],
MIGRATION_NEW,
+ SCHEMA_COMPAT_NEW,
],
];
}
$this->tablesUsed += $this->getMcrTablesToReset();
$this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
$this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
$this->setMwGlobals(
'wgMultiContentRevisionSchemaMigrationStage',
"#REDIRECT [[hello world]]",
"Hello world"
],
+ // The below added to protect against Media namespace
+ // redirects which throw a fatal: (T203942)
+ [
+ 'WikiPageTest_testGetRedirectTarget_3',
+ CONTENT_MODEL_WIKITEXT,
+ "#REDIRECT [[Media:hello_world]]",
+ "File:Hello world"
+ ],
];
}
$this->assertNotContains( 'class="foo bar"', $text );
}
- public function testT203716() {
- // simulate extra wrapping from old parser cache
- $out = new ParserOutput( '<div class="mw-parser-output">Foo</div>' );
- $out = unserialize( serialize( $out ) );
-
- $plainText = $out->getText( [ 'unwrap' => true ] );
- $wrappedText = $out->getText( [ 'unwrap' => false ] );
- $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
- $this->assertNotContains( '<div', $plainText );
- $this->assertContains( '<div', $wrappedText );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
- $this->assertContains( '<div', $wrappedText2 );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
-
- // simulate ParserOuput creation by new parser code
- $out = new ParserOutput( 'Foo' );
- $out->addWrapperDivClass( 'mw-parser-outout' );
- $out = unserialize( serialize( $out ) );
-
- $plainText = $out->getText( [ 'unwrap' => true ] );
- $wrappedText = $out->getText( [ 'unwrap' => false ] );
- $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
- $this->assertNotContains( '<div', $plainText );
- $this->assertContains( '<div', $wrappedText );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
- $this->assertContains( '<div', $wrappedText2 );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
- }
-
/**
* @covers ParserOutput::getText
* @dataProvider provideGetText
}
public function testRcHidemyselfFilter() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "NOT((rc_actor = '{$user->getActorId()}') OR "
- . "(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
+ "NOT((rc_user = '{$user->getId()}'))",
],
[
'hidemyself' => 1,
$id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
+ "NOT((rc_user_text = '10.11.12.13'))",
],
[
'hidemyself' => 1,
}
public function testRcHidebyothersFilter() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "(rc_actor = '{$user->getActorId()}') OR "
- . "(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
+ "(rc_user_text = '{$user->getName()}')",
],
[
'hidebyothers' => 1,
$id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
- "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
+ "(rc_user_text = '10.11.12.13')",
],
[
'hidebyothers' => 1,
}
public function testFilterUserExpLevelAllExperienceLevels() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
- 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ 'rc_user != 0',
],
[
'userExpLevel' => 'newcomer;learner;experienced',
}
public function testFilterUserExpLevelRegistrered() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
- 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ 'rc_user != 0',
],
[
'userExpLevel' => 'registered',
}
public function testFilterUserExpLevelUnregistrered() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
- 'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
+ 'rc_user = 0',
],
[
'userExpLevel' => 'unregistered',
}
public function testFilterUserExpLevelRegistreredOrLearner() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
- 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ 'rc_user != 0',
],
[
'userExpLevel' => 'registered;learner',
}
public function testFilterUserExpLevelUnregistreredOrExperienced() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->setMwGlobals(
+ 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ );
$this->overrideMwServices();
$conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
$this->assertRegExp(
- '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
+ '/\(rc_user = 0\) OR '
. '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
reset( $conds ),
"rc conditions: userExpLevel=unregistered;experienced"
$this->setMwGlobals( [
'wgGroupPermissions' => [],
'wgRevokePermissions' => [],
- 'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
+ 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
] );
$this->overrideMwServices();
$testString .= 'xxx xxx xxx';
}
$testString .= "\n<big id='в'></big>";
- $old = ini_set( 'pcre.backtrack_limit', 200 );
+ $this->setIniSetting( 'pcre.backtrack_limit', 200 );
$result = $this->lc->autoConvert( $testString, 'tg-latn' );
- ini_set( 'pcre.backtrack_limit', $old );
// The в in the id attribute should not get converted to a v
$this->assertFalse(
strpos( $result, 'v' ),