42 public static function newSpec(
PageIdentity $page, array $insertedLinks, array $deletedLinks,
int $batchSize ) {
44 'CategoryCountUpdateJob',
46 'pageId' => $page->
getId(),
48 'insertedLinks' => $insertedLinks,
49 'deletedLinks' => $deletedLinks,
50 'batchSize' => $batchSize,
69 parent::__construct(
'CategoryCountUpdateJob', $page,
$params );
71 $this->connectionProvider = $connectionProvider;
72 $this->namespaceInfo = $namespaceInfo;
76 public function run() {
77 $insertedLinks = $this->params[
'insertedLinks'];
78 $deletedLinks = $this->params[
'deletedLinks'];
80 if ( !$insertedLinks && !$deletedLinks ) {
84 $ticket = $this->connectionProvider->getEmptyTransactionTicket( __METHOD__ );
85 $size = $this->params[
'batchSize'] ?? 100;
88 $this->connectionProvider->commitAndWaitForReplication( __METHOD__, $ticket );
89 if ( count( $insertedLinks ) + count( $deletedLinks ) < $size ) {
90 $this->updateCategoryCounts( $insertedLinks, $deletedLinks );
91 $this->connectionProvider->commitAndWaitForReplication( __METHOD__, $ticket );
93 $addedChunks = array_chunk( $insertedLinks, $size );
94 foreach ( $addedChunks as $chunk ) {
95 $this->updateCategoryCounts( $chunk, [] );
96 if ( count( $addedChunks ) > 1 ) {
97 $this->connectionProvider->commitAndWaitForReplication( __METHOD__, $ticket );
100 $deletedChunks = array_chunk( $deletedLinks, $size );
101 foreach ( $deletedChunks as $chunk ) {
102 $this->updateCategoryCounts( [], $chunk );
103 if ( count( $deletedChunks ) > 1 ) {
104 $this->connectionProvider->commitAndWaitForReplication( __METHOD__, $ticket );
112 private function updateCategoryCounts( array $added, array $deleted ) {
113 $id = $this->params[
'pageId'];
116 $added = array_map(
'strval', $added );
117 $deleted = array_map(
'strval', $deleted );
118 $type = $this->namespaceInfo->getCategoryLinkType( $this->params[
'namespace'] );
120 $addFields = [
'cat_pages' =>
new RawSQLValue(
'cat_pages + 1' ) ];
121 $removeFields = [
'cat_pages' =>
new RawSQLValue(
'cat_pages - 1' ) ];
122 if ( $type !==
'page' ) {
123 $addFields[
"cat_{$type}s"] =
new RawSQLValue(
"cat_{$type}s + 1" );
124 $removeFields[
"cat_{$type}s"] =
new RawSQLValue(
"cat_{$type}s - 1" );
127 $dbw = $this->connectionProvider->getPrimaryDatabase();
128 $res = $dbw->newSelectQueryBuilder()
129 ->select( [
'cat_id',
'cat_title' ] )
131 ->where( [
'cat_title' => array_merge( $added, $deleted ) ] )
132 ->caller( __METHOD__ )
134 $existingCategories = [];
135 foreach ( $res as $row ) {
136 $existingCategories[$row->cat_id] = $row->cat_title;
138 $existingAdded = array_intersect( $existingCategories, $added );
139 $existingDeleted = array_intersect( $existingCategories, $deleted );
140 $missingAdded = array_diff( $added, $existingAdded );
145 if ( $existingAdded ) {
146 $dbw->newUpdateQueryBuilder()
147 ->update(
'category' )
149 ->where( [
'cat_id' => array_keys( $existingAdded ) ] )
150 ->caller( __METHOD__ )->execute();
153 if ( $missingAdded ) {
154 $queryBuilder = $dbw->newInsertQueryBuilder()
155 ->insertInto(
'category' )
156 ->onDuplicateKeyUpdate()
157 ->uniqueIndexFields( [
'cat_title' ] )
159 foreach ( $missingAdded as $cat ) {
160 $queryBuilder->row( [
163 'cat_subcats' => ( $type ===
'subcat' ) ? 1 : 0,
164 'cat_files' => ( $type ===
'file' ) ? 1 : 0,
167 $queryBuilder->caller( __METHOD__ )->execute();
170 if ( $existingDeleted ) {
171 $dbw->newUpdateQueryBuilder()
172 ->update(
'category' )
173 ->set( $removeFields )
174 ->where( [
'cat_id' => array_keys( $existingDeleted ) ] )
175 ->caller( __METHOD__ )->execute();
178 foreach ( $deleted as $catName ) {
179 $cat = Category::newFromName( $catName );
181 DeferredUpdates::addCallableUpdate(
static function () use ( $cat ) {
182 $cat->refreshCountsIfEmpty();