Migrate to IReadableDatabase::newSelectQueryBuilder

Also use expression builder to avoid raw sql

Bug: T312330
Bug: T350954
Change-Id: Iaa1b114954da723bef14d1a59953aeffb85f4a8c
这个提交包含在:
Umherirrender 2024-04-28 00:24:32 +02:00
父节点 2b1aa56370
当前提交 d91fa9a1ba
共有 17 个文件被更改,包括 125 次插入120 次删除

查看文件

@ -20,6 +20,7 @@ use OOUI\ButtonWidget;
use OOUI\IconWidget;
use PageImages\PageImages;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\SelectQueryBuilder;
/**
* This is the "Impact" module. It shows the page views information
@ -585,35 +586,34 @@ class Impact extends BaseModule {
$actorMigration = ActorMigration::newMigration();
$dbr = $this->connectionProvider->getReplicaDatabase();
$actorQuery = $actorMigration->getWhere( $dbr, 'rev_user', $this->getContext()->getUser() );
$subquery = $dbr->buildSelectSubquery(
array_merge( [ 'revision' ], $actorQuery[ 'tables' ], [ 'page' ] ),
[ 'rev_page', 'page_title', 'page_namespace', 'rev_timestamp' ],
[
$subquery = $dbr->newSelectQueryBuilder()
->select( [ 'rev_page', 'page_title', 'page_namespace', 'rev_timestamp' ] )
->from( 'revision' )
->tables( $actorQuery[ 'tables' ] )
->joinConds( $actorQuery[ 'joins' ] )
->join( 'page', null, 'rev_page = page_id' )
->where( [
$actorQuery[ 'conds' ],
'rev_deleted' => 0,
'page_namespace' => 0,
],
__METHOD__,
[ 'ORDER BY' => 'rev_timestamp DESC', 'limit' => 1000 ],
[ 'page' => [ 'JOIN', [ 'rev_page = page_id' ] ] ] + $actorQuery[ 'joins' ]
);
$result = $dbr->select(
[ 'latest_edits' => $subquery ],
[
] )
->orderBy( 'rev_timestamp', SelectQueryBuilder::SORT_DESC )
->limit( 1000 )
->caller( __METHOD__ );
$result = $dbr->newSelectQueryBuilder()
->select( [
'rev_page',
'page_title',
'page_namespace',
'max_ts' => 'MAX(rev_timestamp)',
'min_ts' => 'MIN(rev_timestamp)',
],
[],
__METHOD__,
[
'GROUP BY' => 'rev_page',
'ORDER BY' => 'max_ts DESC',
'LIMIT' => 10,
]
);
] )
->from( $subquery, 'latest_edits' )
->groupBy( 'rev_page' )
->orderBy( 'max_ts', SelectQueryBuilder::SORT_DESC )
->limit( 10 )
->caller( __METHOD__ )
->fetchResultSet();
$contribs = [];
foreach ( $result as $row ) {
$contribs[] = [

查看文件

@ -548,12 +548,12 @@ class UncachedMenteeOverviewDataProvider implements MenteeOverviewDataProvider {
*/
private function getRegistrationTimestampForUsers( array $userIds ): array {
$startTime = microtime( true );
$rows = $this->getReadConnection()->select(
'user',
[ 'user_id', 'user_registration' ],
[ 'user_id' => $userIds ],
__METHOD__
);
$rows = $this->getReadConnection()->newSelectQueryBuilder()
->select( [ 'user_id', 'user_registration' ] )
->from( 'user' )
->where( [ 'user_id' => $userIds ] )
->caller( __METHOD__ )
->fetchResultSet();
$res = [];
foreach ( $rows as $row ) {
$res[$row->user_id] = $row->user_registration;

查看文件

@ -245,10 +245,8 @@ class MentorStatusManager {
->from( 'user_properties' )
->where( [
'up_property' => self::MENTOR_AWAY_TIMESTAMP_PREF,
'up_value IS NOT NULL',
'up_value > ' . $db->addQuotes(
$db->timestamp()
)
$db->expr( 'up_value', '!=', null ),
$db->expr( 'up_value', '>', $db->timestamp() )
] )
->recency( $flags )
->caller( __METHOD__ )

查看文件

@ -15,7 +15,6 @@ use MediaWiki\SpecialPage\Hook\ChangesListSpecialPageStructuredFiltersHook;
use MediaWiki\User\UserIdentity;
use RecentChange;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\SelectQueryBuilder;
/**
* RecentChanges filters for mentors. Separate from MentorHooks because MentorManager
@ -105,7 +104,7 @@ class MentorFilterHooks implements ChangesListSpecialPageStructuredFiltersHook {
// there is a risk of key conflict. Convert into non-associate instead.
// Only apply when $targetIds has at least one ID
if ( $targetActorIds !== [] ) {
$conds[] = $dbr->makeList( [ 'rc_actor' => $targetActorIds ], IDatabase::LIST_AND );
$conds['rc_actor'] = $targetActorIds;
} else {
$conds[] = '0=1';
}
@ -198,11 +197,13 @@ class MentorFilterHooks implements ChangesListSpecialPageStructuredFiltersHook {
// No need to worry about properly acquiring actor IDs - if it shows up in
// recent changes, it already has an actor ID
$queryBuilder = new SelectQueryBuilder( $db );
$queryBuilder->table( 'actor' );
$queryBuilder->field( 'actor_id' );
$queryBuilder->where( [ 'actor_user' => $userIds ] );
return array_map( 'intval', $queryBuilder->fetchFieldValues() );
$res = $db->newSelectQueryBuilder()
->select( 'actor_id' )
->from( 'actor' )
->where( [ 'actor_user' => $userIds ] )
->caller( __METHOD__ )
->fetchFieldValues();
return array_map( 'intval', $res );
}
}

查看文件

@ -16,6 +16,7 @@ use RuntimeException;
use stdClass;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\OrExpressionGroup;
use Wikimedia\Rdbms\SelectQueryBuilder;
/**
@ -134,10 +135,11 @@ class LinkRecommendationStore {
* @return LinkRecommendation[]
*/
public function getAllRecommendations( int $limit, int &$fromPageId ): array {
$res = $this->getDB( DB_REPLICA )->newSelectQueryBuilder()
$dbr = $this->getDB( DB_REPLICA );
$res = $dbr->newSelectQueryBuilder()
->select( [ 'gelr_revision', 'gelr_page', 'gelr_data' ] )
->from( 'growthexperiments_link_recommendations' )
->where( [ 'gelr_page >= ' . $fromPageId ] )
->where( $dbr->expr( 'gelr_page', '>=', $fromPageId ) )
->orderBy( 'gelr_page ASC' )
->limit( $limit )
->caller( __METHOD__ )->fetchResultSet();
@ -161,25 +163,23 @@ class LinkRecommendationStore {
->fetchPageRecords();
$conds = [];
$dbr = $this->loadBalancer->getConnection( DB_REPLICA );
/** @var PageRecord $pageRecord */
foreach ( $pageRecords as $pageRecord ) {
// Making it obvious there's no SQL injection risk is nice, but Phan disagrees.
// @phan-suppress-next-line PhanRedundantConditionInLoop
$pageId = (int)$pageRecord->getId();
$revId = (int)$pageRecord->getLatest();
$pageId = $pageRecord->getId();
$revId = $pageRecord->getLatest();
if ( !$pageId || !$revId ) {
continue;
}
// $revId can be outdated due to replag; we don't want to delete the record then.
$conds[] = "gelr_page = $pageId AND gelr_revision >= $revId";
$conds[] = $dbr->expr( 'gelr_page', '=', $pageId )->and( 'gelr_revision', '>=', $revId );
}
$dbr = $this->loadBalancer->getConnection( DB_REPLICA );
return array_map( 'intval', $dbr->selectFieldValues(
'growthexperiments_link_recommendations',
'gelr_page',
$dbr->makeList( $conds, IDatabase::LIST_OR ),
__METHOD__
) );
return array_map( 'intval', $dbr->newSelectQueryBuilder()
->select( 'gelr_page' )
->from( 'growthexperiments_link_recommendations' )
->where( new OrExpressionGroup( ...$conds ) )
->caller( __METHOD__ )
->fetchFieldValues() );
}
/**
@ -189,11 +189,11 @@ class LinkRecommendationStore {
* @return int[]
*/
public function listPageIds( int $limit, int $from = null ): array {
return array_map( 'intval', $this->loadBalancer->getConnection( DB_REPLICA )
->newSelectQueryBuilder()
$dbr = $this->loadBalancer->getConnection( DB_REPLICA );
return array_map( 'intval', $dbr->newSelectQueryBuilder()
->select( 'gelr_page' )
->from( 'growthexperiments_link_recommendations' )
->where( $from ? [ "gelr_page > $from" ] : [] )
->where( $from ? $dbr->expr( 'gelr_page', '>', $from ) : [] )
->groupBy( 'gelr_page' )
->orderBy( 'gelr_page ASC' )
->limit( $limit )

查看文件

@ -56,15 +56,15 @@ class ProtectionFilter extends AbstractTaskSetFilter implements TaskSetFilter {
// In the longer run, adding batch querying to RestrictionStore itself would be nice.
$results = [];
if ( $validTasks ) {
$results = $this->connectionProvider->getReplicaDatabase()->select(
'page_restrictions',
[ 'pr_page' ],
[
$results = $this->connectionProvider->getReplicaDatabase()->newSelectQueryBuilder()
->select( [ 'pr_page' ] )
->from( 'page_restrictions' )
->where( [
'pr_page' => array_keys( $validTasks ),
'pr_type' => 'edit'
],
__METHOD__
);
] )
->caller( __METHOD__ )
->fetchResultSet();
}
foreach ( $results as $item ) {

查看文件

@ -45,9 +45,9 @@ class NewcomersByMentorMetric implements IMetric {
->where( [
'log_type' => 'newusers',
'log_action' => 'create',
'log_timestamp > ' . $this->dbr->timestamp(
$this->dbr->expr( 'log_timestamp', '>', $this->dbr->timestamp(
(int)wfTimestamp() - ExpirationAwareness::TTL_MONTH
)
) )
] )
->caller( __METHOD__ )
->fetchField();

查看文件

@ -42,14 +42,13 @@ class UserDatabaseHelper {
public function findFirstUserIdForRegistrationTimestamp( $registrationTimestamp ): ?int {
$dbr = $this->connectionProvider->getReplicaDatabase();
$registrationTimestamp = $dbr->timestamp( $registrationTimestamp );
$queryBuilder = new SelectQueryBuilder( $dbr );
$queryBuilder->table( 'user' );
$queryBuilder->field( 'user_id' );
$queryBuilder->where( "user_registration >= " . $dbr->addQuotes( $registrationTimestamp ) );
$queryBuilder->orderBy( 'user_id', SelectQueryBuilder::SORT_ASC );
$queryBuilder->limit( 1 );
$queryBuilder->caller( __METHOD__ );
$userId = $queryBuilder->fetchField();
$userId = $dbr->newSelectQueryBuilder()
->field( 'user_id' )
->from( 'user' )
->where( $dbr->expr( 'user_registration', '>=', $registrationTimestamp ) )
->orderBy( 'user_id', SelectQueryBuilder::SORT_ASC )
->caller( __METHOD__ )
->fetchField();
return $userId === false ? null : (int)$userId;
}
@ -62,19 +61,20 @@ class UserDatabaseHelper {
*/
public function hasMainspaceEdits( UserIdentity $userIdentity, int $limit = 1000 ): ?bool {
$user = $this->userFactory->newFromUserIdentity( $userIdentity );
$queryBuilder = new SelectQueryBuilder( $this->connectionProvider->getReplicaDatabase() );
$queryBuilder->table( 'revision' );
$queryBuilder->join( 'page', null, 'page_id = rev_page' );
$queryBuilder->field( 'page_namespace' );
$queryBuilder->where( [
'rev_actor' => $user->getActorId()
] );
// Look at the user's oldest edits - arbitrary choice, other than we want the method to be
// deterministic. Can be done efficiently via the rev_actor_timestamp index.
$queryBuilder->orderBy( 'rev_timestamp', SelectQueryBuilder::SORT_ASC );
$queryBuilder->limit( $limit );
$queryBuilder->caller( __METHOD__ );
$result = array_map( 'intval', $queryBuilder->fetchFieldValues() );
$res = $this->connectionProvider->getReplicaDatabase()->newSelectQueryBuilder()
->select( 'page_namespace' )
->from( 'revision' )
->join( 'page', null, 'page_id = rev_page' )
->where( [
'rev_actor' => $user->getActorId()
] )
// Look at the user's oldest edits - arbitrary choice, other than we want the method to be
// deterministic. Can be done efficiently via the rev_actor_timestamp index.
->orderBy( 'rev_timestamp', SelectQueryBuilder::SORT_ASC )
->limit( $limit )
->caller( __METHOD__ )
->fetchFieldValues();
$result = array_map( 'intval', $res );
if ( in_array( NS_MAIN, $result ) ) {
return true;
}

查看文件

@ -34,7 +34,6 @@ use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use StatusValue;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\SelectQueryBuilder;
class ComputedUserImpactLookup implements UserImpactLookup {
@ -213,8 +212,8 @@ class ComputedUserImpactLookup implements UserImpactLookup {
private function getEditData( User $user, int $flags ): EditData {
$db = DBAccessObjectUtils::getDBFromRecency( $this->connectionProvider, $flags );
$queryBuilder = new SelectQueryBuilder( $db );
$queryBuilder->table( 'revision' )
$queryBuilder = $db->newSelectQueryBuilder()
->table( 'revision' )
->join( 'page', null, 'rev_page = page_id' );
$taskChangeTagNames = $this->taskTypeHandlerRegistry->getUniqueChangeTags();

查看文件

@ -6,7 +6,6 @@ use IDBAccessObject;
use MediaWiki\User\UserIdentity;
use Wikimedia\AtEase\AtEase;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\SelectQueryBuilder;
class DatabaseUserImpactStore implements UserImpactStore {
@ -43,10 +42,11 @@ class DatabaseUserImpactStore implements UserImpactStore {
}
$userImpacts = array_fill_keys( $userIds, null );
$queryBuilder = new SelectQueryBuilder( $this->loadBalancer->getConnection( DB_REPLICA ) );
$queryBuilder->table( self::TABLE_NAME );
$queryBuilder->fields( [ 'geui_user_id', 'geui_data' ] );
$queryBuilder->where( [ 'geui_user_id' => $userIds ] );
$queryBuilder = $this->loadBalancer->getConnection( DB_REPLICA )->newSelectQueryBuilder()
->select( [ 'geui_user_id', 'geui_data' ] )
->from( self::TABLE_NAME )
->where( [ 'geui_user_id' => $userIds ] )
->caller( __METHOD__ );
foreach ( $queryBuilder->fetchResultSet() as $row ) {
AtEase::suppressWarnings();
$userImpactArray = gzinflate( $row->geui_data );

查看文件

@ -88,8 +88,7 @@ class ExportWelcomeSurveyMailingListData extends Maintenance {
return;
}
$queryBuilderTemplate = new SelectQueryBuilder( $dbr );
$queryBuilderTemplate
$queryBuilderTemplate = $dbr->newSelectQueryBuilder()
->table( 'user' )
->join( 'user_properties', 'survey_prop', [
'user_id = survey_prop.up_user',
@ -113,7 +112,7 @@ class ExportWelcomeSurveyMailingListData extends Maintenance {
->limit( $this->getBatchSize() )
->caller( __METHOD__ );
if ( $toId ) {
$queryBuilderTemplate->where( 'user_id <= ' . $dbr->addQuotes( $toId ) );
$queryBuilderTemplate->where( $dbr->expr( 'user_id', '<=', $toId ) );
}
$userOptionsLookup = $services->getUserOptionsLookup();
@ -125,7 +124,7 @@ class ExportWelcomeSurveyMailingListData extends Maintenance {
$group = $this->getOption( 'group' );
do {
$queryBuilder = clone $queryBuilderTemplate;
$queryBuilder->andWhere( 'user_id > ' . $dbr->addQuotes( $fromId ?? 0 ) );
$queryBuilder->andWhere( $dbr->expr( 'user_id', '>', $fromId ?? 0 ) );
$result = $queryBuilder->fetchResultSet();
foreach ( $result as $row ) {
$fromId = $row->user_id;
@ -172,14 +171,13 @@ class ExportWelcomeSurveyMailingListData extends Maintenance {
* @return int|null
*/
private function getLastUserIdBeforeRegistrationDate( IDatabase $dbr, string $registrationDate ): ?int {
$queryBuilder = new SelectQueryBuilder( $dbr );
$queryBuilder
->fields( [ 'user_id' => 'max(user_id)' ] )
->table( 'user' )
$res = $dbr->newSelectQueryBuilder()
->select( [ 'user_id' => 'max(user_id)' ] )
->from( 'user' )
// Old user records have no registration date. We won't use 'from' dates old enough
// to encounter those so we can ignore them here.
->where( 'user_registration <= ' . $dbr->timestamp( $registrationDate ) );
$res = $queryBuilder->fetchField();
->where( $dbr->expr( 'user_registration', '<=', $registrationDate ) )
->fetchField();
return is_numeric( $res ) ? intval( $res ) : null;
}

查看文件

@ -37,12 +37,13 @@ class DeleteExpiredUserImpactData extends Maintenance {
$expiry = $this->getOption( 'expiry', '30days' );
$expiryTimestamp = $this->getTimestampFromRelativeDate( $expiry );
$queryBuilder = new SelectQueryBuilder( $dbw );
$queryBuilder->table( DatabaseUserImpactStore::TABLE_NAME );
$queryBuilder->field( 'geui_user_id' );
$queryBuilder->where( 'geui_timestamp < ' . $expiryTimestamp );
$queryBuilder->orderBy( 'geui_timestamp', SelectQueryBuilder::SORT_ASC );
$queryBuilder->limit( $this->getBatchSize() );
$queryBuilder = $dbw->newSelectQueryBuilder()
->select( 'geui_user_id' )
->from( DatabaseUserImpactStore::TABLE_NAME )
->where( $dbw->expr( 'geui_timestamp', '<', $dbw->timestamp( $expiryTimestamp ) ) )
->orderBy( 'geui_timestamp', SelectQueryBuilder::SORT_ASC )
->limit( $this->getBatchSize() )
->caller( __METHOD__ );
$deletedCount = 0;
$idsToDelete = $queryBuilder->fetchFieldValues();

查看文件

@ -62,7 +62,7 @@ class DeleteOldSurveys extends Maintenance {
->join( 'user', null, [ 'user_id = up_user' ] )
->where( [
'up_property' => WelcomeSurvey::SURVEY_PROP,
'user_id > ' . $dbr->addQuotes( $fromUserId )
$dbr->expr( 'user_id', '>', $fromUserId )
] )
->orderBy( 'user_id ASC' )
->limit( $this->mBatchSize )

查看文件

@ -79,7 +79,7 @@ class ReassignMentees extends Maintenance {
->from( 'growthexperiments_mentor_mentee' )
->where( [
'gemm_mentor_role' => MentorStore::ROLE_PRIMARY,
'gemm_mentor_id NOT IN (' . $this->growthDbr->makeList( $officialMentorIds ) . ')'
$this->growthDbr->expr( 'gemm_mentor_id', '!=', $officialMentorIds ),
] )
->fetchFieldValues();

查看文件

@ -156,13 +156,14 @@ class RefreshUserImpactData extends Maintenance {
$queryBuilder->limit( $this->getBatchSize() );
$queryBuilder->orderByUserId( SelectQueryBuilder::SORT_ASC );
$lastUserId = (int)$this->getOption( 'fromUser', 0 );
$dbr = $this->getDB( DB_REPLICA );
do {
$this->output( "processing {$this->getBatchSize()} users starting with $lastUserId\n" );
$batchQueryBuilder = clone $queryBuilder;
$batchQueryBuilder->where( 'actor_user > ' . $lastUserId );
$batchQueryBuilder->where( $dbr->expr( 'actor_user', '>', $lastUserId ) );
$userIds = $batchQueryBuilder->fetchFieldValues();
if ( $userIds ) {
$users = $this->actorStore->newSelectQueryBuilder( $this->getDB( DB_REPLICA ) )
$users = $this->actorStore->newSelectQueryBuilder( $dbr )
->whereUserIds( $userIds )
->fetchUserIdentities();
} else {
@ -203,7 +204,7 @@ class RefreshUserImpactData extends Maintenance {
if ( $editedWithin ) {
$timestamp = $dbr->timestamp( $this->getTimestampFromRelativeDate( $editedWithin ) );
$queryBuilder->join( 'revision', null, [ 'rev_actor = actor_id' ] );
$queryBuilder->where( "rev_timestamp >= $timestamp" );
$queryBuilder->where( $dbr->expr( 'rev_timestamp', '>=', $timestamp ) );
$queryBuilder->groupBy( [ 'actor_user' ] );
}
if ( $registeredWithin ) {
@ -211,14 +212,14 @@ class RefreshUserImpactData extends Maintenance {
$this->getTimestampFromRelativeDate( $registeredWithin )
);
if ( $firstUserId ) {
$queryBuilder->where( "actor_user >= $firstUserId" );
$queryBuilder->where( $dbr->expr( 'actor_user', '>=', $firstUserId ) );
} else {
$queryBuilder->where( '0 = 1' );
}
}
if ( $hasEditsAtLeast ) {
$queryBuilder->join( 'user', null, [ 'user_id = actor_user' ] );
$queryBuilder->where( 'user_editcount >= ' . (int)$hasEditsAtLeast );
$queryBuilder->where( $dbr->expr( 'user_editcount', '>=', (int)$hasEditsAtLeast ) );
}
return $queryBuilder;
}

查看文件

@ -38,6 +38,7 @@ use WANObjectCache;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\IReadableDatabase;
use Wikimedia\Rdbms\SelectQueryBuilder;
use Wikimedia\TestingAccessWrapper;
/**
@ -120,10 +121,11 @@ class SuggestedEditsTest extends MediaWikiUnitTestCase {
);
$titleFactoryMock = $this->createMock( TitleFactory::class );
$linkBatchFactoryMock = $this->createMock( LinkBatchFactory::class );
$queryBuilder = $this->createMock( SelectQueryBuilder::class );
$queryBuilder->method( $this->logicalOr( 'select', 'from', 'where', 'caller' ) )->willReturnSelf();
$queryBuilder->method( 'fetchResultSet' )->willReturn( new FakeResultWrapper( [] ) );
$databaseMock = $this->createMock( IReadableDatabase::class );
$databaseMock->expects( $this->once() )
->method( 'select' )
->willReturn( new FakeResultWrapper( [] ) );
$databaseMock->method( 'newSelectQueryBuilder' )->willReturn( $queryBuilder );
$connProvider = $this->createMock( IConnectionProvider::class );
$connProvider->method( 'getReplicaDatabase' )->willReturn( $databaseMock );

查看文件

@ -17,6 +17,7 @@ use stdClass;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\IReadableDatabase;
use Wikimedia\Rdbms\SelectQueryBuilder;
/**
* @covers \GrowthExperiments\NewcomerTasks\ProtectionFilter
@ -105,7 +106,7 @@ class ProtectionFilterTest extends MediaWikiUnitTestCase {
$dbr = $this->createMock( IReadableDatabase::class );
$dbr->expects( $this->exactly( 4 ) )
->method( 'select' )
->with( 'page_restrictions' )
->with( [ 'page_restrictions' ] )
->willReturnCallback(
static function ( $table, $vars, $conds ) use ( $map ) {
$data = [];
@ -122,6 +123,10 @@ class ProtectionFilterTest extends MediaWikiUnitTestCase {
}
return new FakeResultWrapper( $data );
} );
$dbr->method( 'newSelectQueryBuilder' )
->willReturnCallback( static function () use ( $dbr ) {
return new SelectQueryBuilder( $dbr );
} );
$connProvider = $this->createMock( IConnectionProvider::class );
$connProvider->method( 'getReplicaDatabase' )->willReturn( $dbr );
return $connProvider;