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