Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 129 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
FRDependencyUpdate | |
0.00% |
0 / 129 |
|
0.00% |
0 / 10 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
doUpdate | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
182 | |||
getExistingDeps | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
getDepInsertions | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
getDepDeletions | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
42 | |||
makeWhereFrom2d | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
addDependency | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCurrentVersionLinks | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
getCurrentVersionTemplates | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
getCurrentVersionCategories | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | use MediaWiki\Parser\ParserOutput; |
5 | use MediaWiki\Title\Title; |
6 | use Wikimedia\Rdbms\IDBAccessObject; |
7 | use Wikimedia\Rdbms\Platform\ISQLPlatform; |
8 | |
9 | /** |
10 | * Class containing update methods for tracking links that |
11 | * are only in the stable version of pages. Used only for caching. |
12 | */ |
13 | class FRDependencyUpdate { |
14 | /** @var Title */ |
15 | private $title; |
16 | /** @var int[][] */ |
17 | private $sLinks; |
18 | /** @var int[][] */ |
19 | private $sTemplates; |
20 | /** @var string[] */ |
21 | private $sCategoryNames; |
22 | |
23 | // run updates now |
24 | public const IMMEDIATE = 0; |
25 | // use the job queue for updates |
26 | public const DEFERRED = 1; |
27 | |
28 | /** |
29 | * @param Title $title |
30 | * @param ParserOutput $stableOutput |
31 | */ |
32 | public function __construct( Title $title, ParserOutput $stableOutput ) { |
33 | $this->title = $title; |
34 | # Stable version links |
35 | $this->sLinks = $stableOutput->getLinks(); |
36 | $this->sTemplates = $stableOutput->getTemplates(); |
37 | $this->sCategoryNames = $stableOutput->getCategoryNames(); |
38 | } |
39 | |
40 | /** |
41 | * @param int $mode FRDependencyUpdate::IMMEDIATE/FRDependencyUpdate::DEFERRED |
42 | */ |
43 | public function doUpdate( $mode = self::IMMEDIATE ) { |
44 | $deps = []; |
45 | # Get any links that are only in the stable version... |
46 | $cLinks = $this->getCurrentVersionLinks(); |
47 | foreach ( $this->sLinks as $ns => $titles ) { |
48 | foreach ( $titles as $title => $pageId ) { |
49 | if ( !isset( $cLinks[$ns][$title] ) ) { |
50 | $this->addDependency( $deps, $ns, $title ); |
51 | } |
52 | } |
53 | } |
54 | # Get any templates that are only in the stable version... |
55 | $cTemplates = $this->getCurrentVersionTemplates(); |
56 | foreach ( $this->sTemplates as $ns => $titles ) { |
57 | foreach ( $titles as $title => $id ) { |
58 | if ( !isset( $cTemplates[$ns][$title] ) ) { |
59 | $this->addDependency( $deps, $ns, $title ); |
60 | } |
61 | } |
62 | } |
63 | # Get any categories that are only in the stable version... |
64 | $cCategories = $this->getCurrentVersionCategories(); |
65 | foreach ( $this->sCategoryNames as $category ) { |
66 | if ( !isset( $cCategories[$category] ) ) { |
67 | $this->addDependency( $deps, NS_CATEGORY, $category ); |
68 | } |
69 | } |
70 | # Quickly check for any dependency tracking changes (use a replica DB) |
71 | if ( $this->getExistingDeps() != $deps ) { |
72 | if ( $mode === self::DEFERRED ) { |
73 | # Let the job queue parse and update |
74 | MediaWikiServices::getInstance()->getJobQueueGroup()->push( |
75 | new FRExtraCacheUpdateJob( |
76 | $this->title, |
77 | [ 'type' => 'updatelinks' ] |
78 | ) |
79 | ); |
80 | |
81 | return; |
82 | } |
83 | # Determine any dependency tracking changes |
84 | $existing = $this->getExistingDeps( IDBAccessObject::READ_LATEST ); |
85 | $insertions = $this->getDepInsertions( $existing, $deps ); |
86 | $deletions = $this->getDepDeletions( $existing, $deps ); |
87 | $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase(); |
88 | # Delete removed links |
89 | if ( $deletions ) { |
90 | $dbw->newDeleteQueryBuilder() |
91 | ->deleteFrom( 'flaggedrevs_tracking' ) |
92 | ->where( $deletions ) |
93 | ->caller( __METHOD__ ) |
94 | ->execute(); |
95 | } |
96 | # Add any new links |
97 | if ( $insertions ) { |
98 | $dbw->newInsertQueryBuilder() |
99 | ->insertInto( 'flaggedrevs_tracking' ) |
100 | ->ignore() |
101 | ->rows( $insertions ) |
102 | ->caller( __METHOD__ ) |
103 | ->execute(); |
104 | } |
105 | } |
106 | } |
107 | |
108 | /** |
109 | * Get existing cache dependencies |
110 | * @param int $flags One of the IDBAccessObject::READ_… constants |
111 | * @return int[][] (ns => dbKey => 1) |
112 | */ |
113 | private function getExistingDeps( $flags = 0 ) { |
114 | if ( $flags & IDBAccessObject::READ_LATEST ) { |
115 | $db = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase(); |
116 | } else { |
117 | $db = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
118 | } |
119 | $res = $db->newSelectQueryBuilder() |
120 | ->select( [ 'ftr_namespace', 'ftr_title' ] ) |
121 | ->from( 'flaggedrevs_tracking' ) |
122 | ->where( [ 'ftr_from' => $this->title->getArticleID() ] ) |
123 | ->caller( __METHOD__ ) |
124 | ->fetchResultSet(); |
125 | $arr = []; |
126 | foreach ( $res as $row ) { |
127 | $arr[$row->ftr_namespace][$row->ftr_title] = 1; |
128 | } |
129 | return $arr; |
130 | } |
131 | |
132 | /** |
133 | * Get INSERT rows for cache dependencies in $new but not in $existing |
134 | * @param int[][] $existing |
135 | * @param int[][] $new |
136 | * @return array[] |
137 | */ |
138 | private function getDepInsertions( array $existing, array $new ) { |
139 | $arr = []; |
140 | foreach ( $new as $ns => $dbkeys ) { |
141 | if ( isset( $existing[$ns] ) ) { |
142 | $diffs = array_diff_key( $dbkeys, $existing[$ns] ); |
143 | } else { |
144 | $diffs = $dbkeys; |
145 | } |
146 | foreach ( $diffs as $dbk => $id ) { |
147 | $arr[] = [ |
148 | 'ftr_from' => $this->title->getArticleID(), |
149 | 'ftr_namespace' => $ns, |
150 | 'ftr_title' => $dbk |
151 | ]; |
152 | } |
153 | } |
154 | return $arr; |
155 | } |
156 | |
157 | /** |
158 | * Get WHERE clause to delete items in $existing but not in $new |
159 | * @param int[][] $existing |
160 | * @param int[][] $new |
161 | * @return array|false |
162 | */ |
163 | private function getDepDeletions( array $existing, array $new ) { |
164 | $del = []; |
165 | foreach ( $existing as $ns => $dbkeys ) { |
166 | if ( isset( $new[$ns] ) ) { |
167 | $delKeys = array_diff_key( $dbkeys, $new[$ns] ); |
168 | if ( $delKeys ) { |
169 | $del[$ns] = $delKeys; |
170 | } |
171 | } else { |
172 | $del[$ns] = $dbkeys; |
173 | } |
174 | } |
175 | if ( $del ) { |
176 | $dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase(); |
177 | $clause = $this->makeWhereFrom2d( $del, $dbw ); |
178 | if ( $clause ) { |
179 | return [ $clause, 'ftr_from' => $this->title->getArticleID() ]; |
180 | } |
181 | } |
182 | return false; |
183 | } |
184 | |
185 | /** |
186 | * Make WHERE clause to match $arr titles |
187 | * @param array[] $arr |
188 | * @param ISQLPlatform $db |
189 | * @return string|bool |
190 | */ |
191 | private function makeWhereFrom2d( $arr, ISQLPlatform $db ) { |
192 | $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch(); |
193 | $lb->setArray( $arr ); |
194 | return $lb->constructSet( 'ftr', $db ); |
195 | } |
196 | |
197 | /** |
198 | * @param int[][] &$deps |
199 | * @param int $ns |
200 | * @param string $dbKey |
201 | */ |
202 | private function addDependency( array &$deps, $ns, $dbKey ) { |
203 | $deps[$ns][$dbKey] = 1; |
204 | } |
205 | |
206 | /** |
207 | * Get an array of existing links, as a 2-D array |
208 | * @return int[][] (ns => dbKey => 1) |
209 | */ |
210 | private function getCurrentVersionLinks() { |
211 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
212 | $linksMigration = MediaWikiServices::getInstance()->getLinksMigration(); |
213 | $queryInfo = $linksMigration->getQueryInfo( 'pagelinks' ); |
214 | [ $nsField, $titleField ] = $linksMigration->getTitleFields( 'pagelinks' ); |
215 | $res = $dbr->newSelectQueryBuilder() |
216 | ->tables( $queryInfo['tables'] ) |
217 | ->fields( $queryInfo['fields'] ) |
218 | ->where( [ 'pl_from' => $this->title->getArticleID() ] ) |
219 | ->joinConds( $queryInfo['joins'] ) |
220 | ->caller( __METHOD__ ) |
221 | ->fetchResultSet(); |
222 | $arr = []; |
223 | foreach ( $res as $row ) { |
224 | $arr[$row->$nsField][$row->$titleField] = 1; |
225 | } |
226 | return $arr; |
227 | } |
228 | |
229 | /** |
230 | * Get an array of existing templates, as a 2-D array |
231 | * @return int[][] (ns => dbKey => 1) |
232 | */ |
233 | private function getCurrentVersionTemplates() { |
234 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
235 | $linksMigration = MediaWikiServices::getInstance()->getLinksMigration(); |
236 | $queryInfo = $linksMigration->getQueryInfo( 'templatelinks' ); |
237 | [ $nsField, $titleField ] = $linksMigration->getTitleFields( 'templatelinks' ); |
238 | $res = $dbr->newSelectQueryBuilder() |
239 | ->tables( $queryInfo['tables'] ) |
240 | ->fields( $queryInfo['fields'] ) |
241 | ->where( [ 'tl_from' => $this->title->getArticleID() ] ) |
242 | ->joinConds( $queryInfo['joins'] ) |
243 | ->caller( __METHOD__ ) |
244 | ->fetchResultSet(); |
245 | $arr = []; |
246 | foreach ( $res as $row ) { |
247 | $arr[$row->$nsField][$row->$titleField] = 1; |
248 | } |
249 | return $arr; |
250 | } |
251 | |
252 | /** |
253 | * Get an array of existing categories, with the name in the key and sort key in the value. |
254 | * @return string[] (category => sortkey) |
255 | */ |
256 | private function getCurrentVersionCategories() { |
257 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
258 | $res = $dbr->newSelectQueryBuilder() |
259 | ->select( [ 'cl_to', 'cl_sortkey' ] ) |
260 | ->from( 'categorylinks' ) |
261 | ->where( [ 'cl_from' => $this->title->getArticleID() ] ) |
262 | ->caller( __METHOD__ ) |
263 | ->fetchResultSet(); |
264 | $arr = []; |
265 | foreach ( $res as $row ) { |
266 | $arr[$row->cl_to] = $row->cl_sortkey; |
267 | } |
268 | return $arr; |
269 | } |
270 | } |