r57483 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r57482‎ | r57483 | r57484 >
Date:20:10, 7 October 2009
Author:werdna
Status:deferred
Tags:
Comment:
LiquidThreads page-move updates:
* SCHEMA CHANGE: store the article ID as well as the ns/title pair, and keep the two in sync with lazy updates as well as batch updates triggered on delete/move or when a discrepancy is found.
* Add job queue based synchronisation logic for the article ID, NS/title pair.
* When selecting for a talk page, match on both article ID, and NS/title pair.
* Force synchronisation on delete/move, in order to prevent breaking the relationship by (e.g.) moving then immediately deleting.
Modified paths:
  • /trunk/extensions/LiquidThreads/LiquidThreads.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/DeletionController.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/Hooks.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/Thread.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/Threads.php (modified) (history)
  • /trunk/extensions/LiquidThreads/lqt.sql (modified) (history)
  • /trunk/extensions/LiquidThreads/migrateDatabase.php (modified) (history)
  • /trunk/extensions/LiquidThreads/schema-changes/store_article_id.sql (added) (history)

Diff [purge]

Index: trunk/extensions/LiquidThreads/LiquidThreads.php
@@ -67,9 +67,11 @@
6868 $wgHooks['ArticleRevisionUndeleted'][] = 'LqtDeletionController::onArticleRevisionUndeleted';
6969 $wgHooks['ArticleUndelete'][] = 'LqtDeletionController::onArticleUndelete';
7070 $wgHooks['ArticleConfirmDelete'][] = 'LqtDeletionController::onArticleConfirmDelete';
 71+$wgHooks['ArticleDelete'][] = 'LqtDeletionController::onArticleDelete';
7172
7273 // Moving
73 -$wgHooks['SpecialMovepageAfterMove'][] = 'LqtHooks::onArticleMove';
 74+$wgHooks['SpecialMovepageAfterMove'][] = 'LqtHooks::onArticleMoveComplete';
 75+$wgHooks['AbortMove'][] = 'LqtHooks::onArticleMove';
7476
7577 // Search
7678 $wgHooks['ShowSearchHitTitle'][] = 'LqtHooks::customiseSearchResultTitle';
@@ -106,6 +108,7 @@
107109 $wgAutoloadClasses['LqtDeletionController'] = $dir . 'classes/DeletionController.php';
108110 $wgAutoloadClasses['LqtHooks'] = $dir . 'classes/Hooks.php';
109111 $wgAutoloadClasses['ThreadRevision'] = $dir."/classes/ThreadRevision.php";
 112+$wgAutoloadClasses['SynchroniseThreadArticleDataJob'] = "$dir/classes/SynchroniseThreadArticleDataJob.php";
110113
111114 // View classes
112115 $wgAutoloadClasses['TalkpageView'] = $dir . 'pages/TalkpageView.php';
@@ -127,6 +130,9 @@
128131 $wgAutoloadClasses['SpecialSplitThread'] = $dir . 'pages/SpecialSplitThread.php';
129132 $wgAutoloadClasses['SpecialMergeThread'] = $dir . 'pages/SpecialMergeThread.php';
130133
 134+// Job queue
 135+$wgJobClasses['synchroniseThreadArticleData'] = 'SynchroniseThreadArticleDataJob';
 136+
131137 // Backwards-compatibility
132138 $wgAutoloadClasses['Article_LQT_Compat'] = $dir . 'compat/LqtCompatArticle.php';
133139 if ( version_compare( $wgVersion, '1.16', '<' ) ) {
Index: trunk/extensions/LiquidThreads/classes/Threads.php
@@ -162,10 +162,18 @@
163163 static function articleClause( $article ) {
164164 $dbr = wfGetDB( DB_SLAVE );
165165
166 - $arr = array( 'thread_article_title' => $article->getTitle()->getDBKey(),
 166+ $titleCond = array( 'thread_article_title' => $article->getTitle()->getDBKey(),
167167 'thread_article_namespace' => $article->getTitle()->getNamespace() );
 168+ $titleCond = $dbr->makeList( $titleCond, LIST_AND );
168169
169 - return $dbr->makeList( $arr, LIST_AND );
 170+ $conds = array( $titleCond );
 171+
 172+ if ( $article->getId() ) {
 173+ $idCond = array( 'thread_article_id' => $article->getId() );
 174+ $conds[] = $dbr->makeList( $idCond, LIST_AND );
 175+ }
 176+
 177+ return $dbr->makeList( $conds, LIST_OR );
170178 }
171179
172180 static function topLevelClause() {
@@ -241,5 +249,65 @@
242250 $i++;
243251 }
244252 return $t;
245 - }
 253+ }
 254+
 255+ // Called just before any function that might cause a loss of article association.
 256+ // by breaking either a NS-title reference (by moving the article), or a page-id
 257+ // reference (by deleting the article).
 258+ // Basically ensures that all subthreads have the two stores of article association
 259+ // synchronised.
 260+ // Can also be called with a "limit" parameter to slowly convert old threads. This
 261+ // is intended to be used by jobs created by move and create operations to slowly
 262+ // propagate the change through the data set without rushing the whole conversion
 263+ // when a second breaking change is made. If a limit is set and more rows require
 264+ // conversion, this function will return false. Otherwise, true will be returned.
 265+ // If the queueMore parameter is set and rows are left to update, a job queue item
 266+ // will then be added with the same limit, to finish the remainder of the update.
 267+ static function synchroniseArticleData( $article, $limit = false, $queueMore = false ) {
 268+ $dbr = wfGetDB( DB_SLAVE );
 269+ $dbw = wfGetDB( DB_MASTER );
 270+
 271+ $title = $article->getTitle();
 272+ $id = $article->getId();
 273+
 274+ $titleCond = array( 'thread_article_namespace' => $title->getNamespace(),
 275+ 'thread_article_title' => $title->getDBkey() );
 276+ $titleCondText = $dbr->makeList( $titleCond, LIST_AND );
 277+
 278+ $idCond = array( 'thread_article_id' => $id );
 279+ $idCondText = $dbr->makeList( $idCond, LIST_AND );
 280+
 281+ $fixTitleCond = array( $idCondText, "NOT ($titleCondText)" );
 282+ $fixIdCond = array( $titleCondText, "NOT ($idCondText)" );
 283+
 284+ // Try to hit the most recent threads first.
 285+ $options = array( 'LIMIT' => 500, 'ORDER BY' => 'thread_id DESC' );
 286+
 287+ // Batch in 500s
 288+ if ($limit) $options['LIMIT'] = min( $limit, 500 );
 289+
 290+ $rowsAffected = 0;
 291+ $roundRowsAffected = 1;
 292+ while( (!$limit || $rowsAffected < $limit) && $roundRowsAffected > 0 ) {
 293+ $roundRowsAffected = 0;
 294+
 295+ // Fix wrong title.
 296+ $res = $dbw->update( 'thread', $titleCond, $fixTitleCond, __METHOD__, $options );
 297+ $roundRowsAffected += $dbw->affectedRows();
 298+
 299+ // Fix wrong ID
 300+ $res = $dbw->update( 'thread', $idCond, $fixIdCond, __METHOD__, $options );
 301+ $roundRowsAffected += $dbw->affectedRows();
 302+
 303+ $rowsAffected += $roundRowsAffected;
 304+ }
 305+
 306+ if ( $limit && ($rowsAffected >= $limit) && $queueMore ) {
 307+ $jobParams = array( 'limit' => $limit, 'cascade' => true );
 308+ $job = new SynchroniseThreadArticleDataJob( $article->getTitle(), $jobParams );
 309+ $job->insert();
 310+ }
 311+
 312+ return $limit ? ($rowsAffected < $limit) : true;
 313+ }
246314 }
Index: trunk/extensions/LiquidThreads/classes/Hooks.php
@@ -326,33 +326,31 @@
327327 $wgExtNewFields[] = array( "thread", "thread_author_name", "$dir/schema-changes/store_subject-author.sql" );
328328 $wgExtNewFields[] = array( "thread", "thread_sortkey", "$dir/schema-changes/new-sortkey.sql" );
329329 $wgExtNewFields[] = array( 'thread', 'thread_replies', "$dir/schema-changes/store_reply_count.sql" );
 330+ $wgExtNewFields[] = array( 'thread', 'thread_article_id', "$dir/schema-changes/store_article_id.sql" );
330331
331332 $wgExtNewIndexes[] = array( 'thread', 'thread_summary_page', '(thread_summary_page)' );
332333
333334 return true;
334335 }
335336
336 - static function onArticleMove( &$form, &$ot, &$nt ) {
 337+ static function onArticleMoveComplete( &$form, &$ot, &$nt ) {
337338 // Check if it's a talk page.
338339 if ( !LqtDispatch::isLqtPage( $ot ) && !LqtDispatch::isLqtPage( $nt ) ) {
339340 return true;
340341 }
341342
342 - // Find and move all threads, using the job queue if necessary.
343 - $otCond = array( 'thread_article_namespace' => $ot->getNamespace(),
344 - 'thread_article_title' => $ot->getDBkey() );
 343+ // Synchronise the first 500 threads, in reverse order by thread id. If
 344+ // there are more threads to synchronise, the job queue will take over.
 345+ Threads::synchroniseArticleData( new Article( $nt ), 500, 'cascade' );
345346
346 - $ntCond = array( 'thread_article_namespace' => $nt->getNamespace(),
347 - 'thread_article_title' => $nt->getDBkey() );
348 -
349 - $dbw = wfGetDB( DB_MASTER );
350 - $rowCountEst = $dbw->estimateRowCount( 'thread', '*', $otCond, __METHOD__ );
 347+ return true;
 348+ }
 349+
 350+ static function onArticleMove( $ot, $nt, $user, &$err, $reason ) {
 351+ // Synchronise article data so that moving the article doesn't break any
 352+ // article association.
 353+ Threads::synchroniseArticleData( new Article( $ot ) );
351354
352 - wfDebug( "Row count estimate is $rowCountEst\n" );
353 -
354 -
355 - $dbw->update( 'thread', $ntCond, $otCond, __METHOD__ );
356 -
357355 return true;
358356 }
359357
Index: trunk/extensions/LiquidThreads/classes/DeletionController.php
@@ -36,6 +36,10 @@
3737 }
3838 }
3939
 40+ // Synchronise the first 500 threads, in reverse order by thread id. If
 41+ // there are more threads to synchronise, the job queue will take over.
 42+ Threads::synchroniseArticleData( $article, 500, 'cascade' );
 43+
4044 return true;
4145 }
4246
@@ -85,6 +89,10 @@
8690 }
8791 }
8892
 93+ // Synchronise the first 500 threads, in reverse order by thread id. If
 94+ // there are more threads to synchronise, the job queue will take over.
 95+ Threads::synchroniseArticleData( new Article( $udTitle ), 500, 'cascade' );
 96+
8997 return true;
9098 }
9199
@@ -103,4 +111,12 @@
104112
105113 return true;
106114 }
 115+
 116+ static function onArticleDelete( $article ) {
 117+ // Synchronise article data so that moving the article doesn't break any
 118+ // article association.
 119+ Threads::synchroniseArticleData( $article );
 120+
 121+ return true;
 122+ }
107123 }
Index: trunk/extensions/LiquidThreads/classes/Thread.php
@@ -360,6 +360,7 @@
361361 'thread_root' => 'rootId',
362362 'thread_article_namespace' => 'articleNamespace',
363363 'thread_article_title' => 'articleTitle',
 364+ 'thread_article_id' => 'articleId',
364365 'thread_summary_page' => 'summaryId',
365366 'thread_ancestor' => 'ancestorId',
366367 'thread_parent' => 'parentId',
@@ -696,6 +697,42 @@
697698 $set['thread_replies'] = $count;
698699 }
699700
 701+ // Check for invalid/missing articleId
 702+ $articleTitle = null;
 703+ $dbTitle = Title::makeTitleSafe( $this->articleNamespace, $this->articleTitle );
 704+ if ($this->articleId && isset(self::$titleCacheById[$this->articleId]) ) {
 705+ // If it corresponds to a title, the article obviously exists.
 706+ $articleTitle = self::$titleCacheById[$this->articleId];
 707+ $this->article = new Article_LQT_Compat( $articleTitle );
 708+ } elseif( $this->articleId ) {
 709+ $articleTitle = Title::newFromID( $this->articleId );
 710+ }
 711+
 712+ // If still unfilled, the article ID referred to is no longer valid. Re-fill it
 713+ // from the namespace/title pair if an article ID is provided
 714+ if ( !$articleTitle && ( $this->articleId != 0 || $dbTitle->getArticleId() != 0 ) ) {
 715+ $articleTitle = $dbTitle;
 716+ $this->articleId = $articleTitle->getArticleId();
 717+ $this->article = new Article_LQT_Compat( $dbTitle );
 718+
 719+ $set['thread_article_id'] = $this->articleId;
 720+ wfDebug( "Unfilled or non-existent thread_article_id, refilling to {$this->articleId}\n" );
 721+
 722+ // There are probably problems on the rest of the article, trigger a small update
 723+ Threads::synchroniseArticleData( $this->article, 100, 'cascade' );
 724+ } elseif ( $articleTitle && !$articleTitle->equals( $dbTitle ) ) {
 725+ // The page was probably moved and this was probably not updated.
 726+ wfDebug( "Article ID/Title discrepancy, resetting NS/Title to article provided by ID\n" );
 727+ $this->articleNamespace = $articleTitle->getNamespace();
 728+ $this->articleTitle = $articleTitle->getDBkey();
 729+
 730+ $set['thread_article_namespace'] = $articleTitle->getNamespace();
 731+ $set['thread_article_title'] = $articleTitle->getDBkey();
 732+
 733+ // There are probably problems on the rest of the article, trigger a small update
 734+ Threads::synchroniseArticleData( $this->article, 100, 'cascade' );
 735+ }
 736+
700737 if ( count($set) ) {
701738 $dbw = wfGetDB( DB_MASTER );
702739
Index: trunk/extensions/LiquidThreads/lqt.sql
@@ -15,6 +15,7 @@
1616
1717 thread_article_namespace int NOT NULL,
1818 thread_article_title varchar(255) binary NOT NULL,
 19+ thread_article_id int(8) unsigned NOT NULL,
1920
2021 -- Special thread types (deleted/move trace/normal)
2122 thread_type int(4) unsigned NOT NULL default 0,
@@ -29,6 +30,7 @@
3031 UNIQUE INDEX thread_root_page (thread_root),
3132 INDEX thread_ancestor (thread_ancestor, thread_parent),
3233 INDEX thread_article_title (thread_article_namespace, thread_article_title, thread_sortkey),
 34+ INDEX thread_article (thread_article_id, thread_sortkey),
3335 INDEX thread_modified (thread_modified),
3436 INDEX thread_created (thread_created),
3537 INDEX thread_summary_page (thread_summary_page),
Index: trunk/extensions/LiquidThreads/migrateDatabase.php
@@ -9,6 +9,8 @@
1010
1111 $db = wfGetDB( DB_MASTER );
1212
 13+$wgTitle = Title::makeTitleSafe( NS_SPECIAL, 'LiquidThreads' );
 14+
1315 // Do database updates
1416 $threadFieldUpdates = array('thread_article_namespace' => 'split-thread_article.sql',
1517 'thread_article_title' => 'split-thread_article.sql',
@@ -22,6 +24,7 @@
2325 'thread_author_name' => 'store_subject-author.sql',
2426 'thread_sortkey' => 'new-sortkey.sql',
2527 'thread_replies' => 'store_reply_count.sql',
 28+ 'thread_article_id' => 'store_article_id.sql',
2629 );
2730 $threadIndexUpdates = array('thread_summary_page' => 'index-summary_page.sql');
2831
Index: trunk/extensions/LiquidThreads/schema-changes/store_article_id.sql
@@ -0,0 +1,3 @@
 2+-- Add the thread_article_id field. Populated on-demand in Thread.php
 3+ALTER TABLE /*_*/thread ADD COLUMN thread_article_id int(8) unsigned NOT NULL;
 4+ALTER TABLE /*_*/thread ADD INDEX thread_article (thread_article_id, thread_sortkey);

Status & tagging log