Index: branches/liquidthreads/maintenance/lqt.sql |
— | — | @@ -1,30 +1,13 @@ |
2 | | -CREATE TABLE /*$wgDBprefix*/lqt_thread ( |
| 2 | +CREATE TABLE /*$wgDBprefix*/thread ( |
3 | 3 | thread_id int(8) unsigned NOT NULL auto_increment, |
4 | | - |
5 | | - thread_root_post int(8) unsigned NOT NULL, |
6 | | - |
7 | | - -- Article that this thread belongs to. |
| 4 | + thread_root int(8) unsigned NOT NULL, |
8 | 5 | thread_article int(8) unsigned NOT NULL, |
9 | | - |
10 | | - -- Thread this is a subthread (reply) of. NULL if top-level to article. |
11 | | - thread_subthread_of int(8) unsigned NULL, |
12 | | - |
13 | | - -- Summary post: |
| 6 | + thread_path varchar(1000000) NOT NULL, |
14 | 7 | thread_summary_page int(8) unsigned NULL, |
15 | | - |
16 | | - -- Subject line string: |
17 | | - thread_subject varchar(255) binary NULL, |
18 | | - |
19 | | - -- Timestamp |
20 | 8 | thread_touched char(14) binary NOT NULL default '', |
21 | 9 | |
22 | 10 | PRIMARY KEY thread_id (thread_id), |
23 | | - UNIQUE INDEX thread_id (thread_id), |
24 | | - INDEX thread_subthread_of (thread_subthread_of), |
25 | | - INDEX thread_article_touched (thread_article, thread_touched), |
26 | | - INDEX thread_article (thread_article), |
27 | | - INDEX thread_root_post (thread_root_post) |
28 | | - |
| 11 | + UNIQUE INDEX thread_id (thread_id) |
29 | 12 | ) TYPE=InnoDB; |
30 | 13 | |
31 | 14 | /* |
Index: branches/liquidthreads/extensions/LqtExtension.php |
— | — | @@ -596,6 +596,9 @@ |
597 | 597 | $this->output->setPageTitle( "Talk:" . $this->title->getText() ); // TODO non-main namespaces. |
598 | 598 | $this->addJSandCSS(); |
599 | 599 | |
| 600 | + lqtCheapTest( ); |
| 601 | + return; |
| 602 | + |
600 | 603 | $this->showHeader(); |
601 | 604 | |
602 | 605 | $this->showArchiveWidget(); |
Index: branches/liquidthreads/extensions/LqtModel.php |
— | — | @@ -101,247 +101,98 @@ |
102 | 102 | |
103 | 103 | } |
104 | 104 | |
105 | | -class Thread { |
| 105 | +class LiveThread { |
106 | 106 | |
107 | | - /* ID references to other objects that are loaded on demand: */ |
108 | | - protected $rootPostId; |
109 | | - protected $articleId; |
110 | | - protected $summaryId; |
111 | | - protected $superthreadId; |
| 107 | +} |
112 | 108 | |
113 | | - /* Actual objects loaded on demand from the above when accessors are called: */ |
114 | | - protected $rootPost; |
115 | | - protected $article; |
116 | | - protected $summary; |
117 | | - protected $superthread; |
118 | | - |
119 | | - /* Simple strings: */ |
120 | | - protected $subject; |
121 | | - protected $touched; |
| 109 | +/** Module of factory methods. */ |
| 110 | +class Threads { |
122 | 111 | |
123 | | - /* Identity */ |
124 | | - protected $id; |
125 | | - |
126 | | - function setSuperthread($thread) { |
127 | | - $this->superthreadId = $thread->id(); |
128 | | - $this->touch(); |
129 | | - } |
130 | | - |
131 | | - function superthread() { |
132 | | - if ( !$this->superthreadId ) return null; |
133 | | - if ( !$this->superthread ) $this->superthread = Thread::newFromId($this->superthreadId); |
134 | | - return $this->superthread; |
135 | | - } |
136 | | - |
137 | | - function topmostThread() { |
138 | | - if ( !$this->superthread() ) return $this; |
139 | | - else return $this->superthread()->topmostThread(); |
140 | | - } |
141 | | - |
142 | | - function setArticle($a) { |
143 | | - $this->articleId = $a->getID(); |
144 | | - $this->touch(); |
145 | | - } |
146 | | - |
147 | | - function article() { |
148 | | - if ( !$this->articleId ) return null; |
149 | | - if ( !$this->article ) $this->article = new Article(Title::newFromID($this->articleId)); |
150 | | - return $this->article; |
151 | | - } |
152 | | - |
153 | | - function id() { |
154 | | - return $this->id; |
155 | | - } |
156 | | - |
157 | | - function rootPost() { |
158 | | - if ( !$this->rootPostId ) return null; |
159 | | - if ( !$this->rootPost ) $this->rootPost = new Post( Title::newFromID( $this->rootPostId ) ); |
160 | | - return $this->rootPost; |
161 | | - } |
162 | | - |
163 | | - function summary() { |
164 | | - if ( !$this->summaryId ) return null; |
165 | | - if ( !$this->summary ) $this->summary = new Post( Title::newFromID( $this->summaryId ) ); |
166 | | - return $this->summary; |
167 | | - } |
168 | | - |
169 | | - function setSummary( $post ) { |
170 | | - $this->summaryId = $post->getID(); |
171 | | - $this->updateRecord(); |
172 | | - } |
173 | | - |
174 | | - function wikilink() { |
175 | | - return $this->rootPost()->getTitle()->getPrefixedText(); |
176 | | - } |
177 | | - |
178 | | - function wikilinkWithoutIncrement() { |
179 | | - $foo = explode( ' ', $this->wikilink() ); |
180 | | - array_pop($foo); |
181 | | - return implode( ' ', $foo ); |
182 | | - } |
183 | | - |
184 | | - function hasDistinctSubject() { |
185 | | - if( $this->superthread() ) { |
186 | | - return $this->superthread()->subjectWithoutIncrement() |
187 | | - != $this->subjectWithoutIncrement(); |
| 112 | + static function threadsWhere( $where_clause, $options = array(), $extra_tables = array() ) { |
| 113 | + $dbr =& wfGetDB( DB_SLAVE ); |
| 114 | + if ( is_array($where_clause) ) { |
| 115 | + $where = implode( 'and ', $where_clause ); |
188 | 116 | } else { |
189 | | - return true; |
| 117 | + $where = $where_clause; |
190 | 118 | } |
191 | | - } |
192 | 119 | |
193 | | - function subject() { |
194 | | - return $this->rootPost()->getTitle()->getText(); |
195 | | - return $this->subject; |
196 | | - } |
197 | | - |
198 | | - function subjectWithoutIncrement() { |
199 | | - $foo = explode( ' ', $this->subject() ); |
200 | | - array_pop($foo); |
201 | | - return implode( ' ', $foo ); |
202 | | - } |
203 | | - |
204 | | - function increment() { |
205 | | - return array_pop( explode(' ', $this->subject()) ); |
206 | | - } |
207 | | - |
208 | | - function setSubject($s) { |
209 | | - $this->subject = $s; |
210 | | - $this->touch(); |
211 | | - } |
| 120 | + /* Select the client's threads, AND all their children: */ |
212 | 121 | |
213 | | - function hasSubthreads() { |
214 | | - // TODO inefficient. |
215 | | - return count( $this->subthreads() ) != 0; |
216 | | - } |
| 122 | + $sql = <<< SQL |
| 123 | +SELECT children.* FROM thread, thread children |
| 124 | +WHERE $where AND |
| 125 | +children.thread_path LIKE CONCAT(thread.thread_path, "%") |
| 126 | +SQL; |
| 127 | + $res = $dbr->query($sql); |
217 | 128 | |
218 | | - function subthreads() { |
219 | | - return Thread::threadsWhere( array('thread_subthread_of' => $this->id), |
220 | | - array('ORDER BY' => 'thread_touched') ); |
221 | | - } |
222 | | - |
223 | | - function touch() { |
224 | | - $this->touched = wfTimestampNow(); |
225 | | - $this->updateRecord(); |
226 | | - if ( $this->superthread() ) { |
227 | | - $this->superthread()->touch(); |
| 129 | + /* |
| 130 | + God probably kills a kitten whenever this next section of code is run. |
| 131 | + We're creating a tree of objects from the flat list of rows. Please someone |
| 132 | + think of a way to do this in one pass. |
| 133 | + */ |
| 134 | + |
| 135 | + $tree = array(); |
| 136 | + while ( $line = $dbr->fetchObject($res) ) { |
| 137 | + $path = explode('.', $line->thread_path); |
| 138 | + Threads::setDeepArray( $tree, $line, $path ); |
| 139 | + |
228 | 140 | } |
| 141 | + function createThreads( $thread ) { |
| 142 | + $subthreads = array(); |
| 143 | + foreach( $thread as $key => $val ) { |
| 144 | + if ( $key != 'root' ) { |
| 145 | + $subthreads[] = createThreads( $val ); |
| 146 | + } |
| 147 | + } |
| 148 | + return Threads::newLiveThreadFromDBLine( $thread['root'], $subthreads ); |
| 149 | + } |
| 150 | + $threads = array(); |
| 151 | + foreach( $tree as $root ) { |
| 152 | + $threads[] = createThreads($root); |
| 153 | + } |
| 154 | + |
| 155 | + return $threads; |
229 | 156 | } |
230 | | - |
231 | | - function touched() { |
232 | | - return $this->touched; |
233 | | - } |
234 | | - |
235 | | - protected function updateRecord() { |
236 | | - $dbr =& wfGetDB( DB_MASTER ); |
237 | | - $res = $dbr->update( 'lqt_thread', |
238 | | - /* SET */ array( 'thread_root_post' => $this->rootPostId, |
239 | | - 'thread_article' => $this->articleId, |
240 | | - 'thread_subthread_of' => $this->superthreadId, |
241 | | - 'thread_summary_page' => $this->summaryId, |
242 | | - 'thread_subject' => $this->subject, |
243 | | - 'thread_touched' => $this->touched ), |
244 | | - /* WHERE */ array( 'thread_id' => $this->id, ), |
245 | | - __METHOD__); |
246 | | - } |
247 | 157 | |
248 | | - static function newFromDBLine( $line ) { |
249 | | - $t = new Thread(); |
| 158 | + /** setDeepArray( $a, $v, array(1,2,3) ) <=> $a[1][2][3]['root'] = $v; */ |
| 159 | + private static function setDeepArray( &$a, $v, $p ) { |
| 160 | + if( count($p) == 1 ) { |
| 161 | + $a[$p[0]]["root"] = $v; |
| 162 | + } else { |
| 163 | + if( !array_key_exists( $p[0], $a ) ) |
| 164 | + $a[$p[0]] = array(); |
| 165 | + Threads::setDeepArray( $a[$p[0]], $v, array_slice($p, 1) ); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + static function newLiveThreadFromDBLine( $line, $children ) { |
| 170 | + $t = new LiveThread(); |
250 | 171 | $t->id = $line->thread_id; |
251 | | - $t->rootPostId = $line->thread_root_post; |
| 172 | + $t->rootId = $line->thread_root; |
252 | 173 | $t->articleId = $line->thread_article; |
253 | 174 | $t->summaryId = $line->thread_summary_page; |
254 | | - $t->superthreadId = $line->thread_subthread_of; |
| 175 | + $t->path = $line->thread_path; |
255 | 176 | $t->touched = $line->thread_touched; |
256 | | - $t->subject = $line->thread_subject; |
| 177 | + $t->replies = $children; |
257 | 178 | return $t; |
258 | 179 | } |
259 | 180 | |
260 | | - static function newFromId( $id ) { |
261 | | - $foo = Thread::threadsWhere( array('thread_id' => $id) ); |
262 | | - return count($foo) > 0 ? $foo[0] : null; |
263 | | - } |
| 181 | +} |
264 | 182 | |
265 | | - static function newThread( $root_post, $article ) { |
266 | | - $dbr =& wfGetDB( DB_MASTER ); |
267 | | - $res = $dbr->insert('lqt_thread', |
268 | | - array('thread_article' => $article->getID(), |
269 | | - 'thread_root_post' => $root_post->getID(), |
270 | | - 'thread_touched' => wfTimestampNow()), |
271 | | - __METHOD__); |
272 | | - // TODO we could avoid a query here. |
273 | | - return Thread::newFromId( $dbr->insertId() ); |
274 | | - } |
275 | | - |
276 | | - /** List of months in which there are >0 threads, suitable for threadsOfArticleInMonth. */ |
277 | | - static function monthsWhereArticleHasThreads( $article ) { |
278 | | - $threads = Thread::allThreadsOfArticle( $article ); |
279 | | - $months = array(); |
280 | | - foreach( $threads as $t ) { |
281 | | - $m = substr( $t->touched(), 0, 6 ); |
282 | | - if ( !array_key_exists( $m, $months ) ) { |
283 | | - if (!in_array( $m, $months )) $months[] = $m; |
284 | | - } |
| 183 | +function lqtCheapTest() { |
| 184 | + $threads = Threads::threadsWhere( "thread.thread_id = 1" ); |
| 185 | + function cheapShowThread($t) { |
| 186 | + global $wgOut; |
| 187 | + $wgOut->addHTML($t->id); |
| 188 | + $wgOut->addHTML('<dl><dd>'); |
| 189 | + foreach( $t->replies as $r ) { |
| 190 | + cheapShowThread($r); |
285 | 191 | } |
286 | | - return $months; |
| 192 | + $wgOut->addHTML('</dd></dl>'); |
287 | 193 | } |
288 | | - |
289 | | - static function latestNThreadsOfArticle( $article, $n ) { |
290 | | - return Thread::threadsWhere( array('thread_article' => $article->getID(), |
291 | | - 'thread_subthread_of is null'), |
292 | | - array('ORDER BY' => 'thread_touched DESC', |
293 | | - 'LIMIT' => $n) ); |
| 194 | + foreach( $threads as $t ) { |
| 195 | + cheapShowThread($t); |
294 | 196 | } |
295 | | - |
296 | | - static function allThreadsOfArticle( $article ) { |
297 | | - return Thread::threadsWhere( array('thread_article' => $article->getID(), |
298 | | - 'thread_subthread_of is null'), |
299 | | - array('ORDER BY' => 'thread_touched DESC') ); |
300 | | - } |
301 | | - |
302 | | - static function threadsOfArticleInMonth( $article, $yyyymm ) { |
303 | | - return Thread::threadsWhere( array('thread_article' => $article->getID(), |
304 | | - 'thread_subthread_of is null', |
305 | | - 'thread_touched >= "'.Date::beginningOfMonth($yyyymm).'"', |
306 | | - 'thread_touched <= "'.Date::endOfMonth($yyyymm).'"'), |
307 | | - array('ORDER BY' => 'thread_touched DESC') ); |
308 | | - } |
309 | | - |
310 | | - static function threadsOfArticleInLastNDays( $article, $n ) { |
311 | | - $startdate = Date::now()->nDaysAgo($n)->midnight(); |
312 | | - return Thread::threadsWhere( array('thread_article' => $article->getID(), |
313 | | - 'thread_subthread_of is null', |
314 | | - 'thread_touched >= ' . $startdate->text() ), |
315 | | - array('ORDER BY' => 'thread_touched DESC' ) ); |
316 | | - } |
317 | | - |
318 | | - static function threadsWhoseRootPostIs( $post ) { |
319 | | - return Thread::threadsWhere( array('thread_root_post' => $post->getID()) ); |
320 | | - } |
321 | | - |
322 | | - static function threadsWhere( $where_clause, $options = array(), $extra_tables = array() ) { |
323 | | - $dbr =& wfGetDB( DB_SLAVE ); |
324 | | - $res = $dbr->select( array_merge(array('lqt_thread'), $extra_tables), |
325 | | - array('lqt_thread.*'), |
326 | | - $where_clause, |
327 | | - __METHOD__, |
328 | | - $options); |
329 | | - $threads = array(); |
330 | | - while ( $line = $dbr->fetchObject($res) ) { |
331 | | - $threads[] = Thread::newFromDBLine( $line ); |
332 | | - } |
333 | | - return $threads; |
334 | | - } |
335 | | - |
336 | | - static function walk( $root, $thread_callback, $push_callback, $pop_callback ) { |
337 | | - call_user_func($thread_callback, $root); |
338 | | - $s = $root->subthreads(); if ($s) { |
339 | | - call_user_func($push_callback); |
340 | | - foreach ($s as $t) { |
341 | | - Thread::walk($t, $thread_callback, $push_callback, $pop_callback); |
342 | | - } |
343 | | - call_user_func($pop_callback); |
344 | | - } |
345 | | - } |
346 | 197 | } |
347 | 198 | |
348 | 199 | class QueryGroup { |