Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.77% |
59 / 65 |
|
84.62% |
11 / 13 |
CRAP | |
0.00% |
0 / 1 |
GenericPageLinksTable | |
90.77% |
59 / 65 |
|
84.62% |
11 / 13 |
27.57 | |
0.00% |
0 / 1 |
getNamespaceField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getTitleField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getTargetIdField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getFromNamespaceField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getExistingFields | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
getExistingLinks | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
fetchExistingRows | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
getNewLinkIDs | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getExistingLinkIDs | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
isExisting | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isInNewSet | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
insertLink | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
deleteLink | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
needForcedLinkRefresh | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
makePageReferenceValue | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
deduplicateLinkIds | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Deferred\LinksUpdate; |
4 | |
5 | use MediaWiki\DAO\WikiAwareEntity; |
6 | use MediaWiki\Page\PageReferenceValue; |
7 | use MediaWiki\Title\Title; |
8 | use Wikimedia\Rdbms\IResultWrapper; |
9 | |
10 | /** |
11 | * Shared code for pagelinks and templatelinks. They are very similar tables |
12 | * since they both link to an arbitrary page identified by namespace and title. |
13 | * |
14 | * Link ID format: string[]: |
15 | * - 0: namespace ID |
16 | * - 1: title DB key |
17 | * |
18 | * @since 1.38 |
19 | */ |
20 | abstract class GenericPageLinksTable extends TitleLinksTable { |
21 | /** |
22 | * A 2d array representing the new links, with the namespace ID in the |
23 | * first key, the DB key in the second key, and the value arbitrary. |
24 | * |
25 | * @var array |
26 | */ |
27 | protected $newLinks = []; |
28 | |
29 | /** |
30 | * The existing links in the same format as self::$newLinks, or null if it |
31 | * has not been loaded yet. |
32 | * |
33 | * @var array|null |
34 | */ |
35 | private $existingLinks; |
36 | |
37 | /** |
38 | * Get the namespace field name |
39 | * |
40 | * @return string |
41 | */ |
42 | abstract protected function getNamespaceField(); |
43 | |
44 | /** |
45 | * Get the title (DB key) field name |
46 | * |
47 | * @return string |
48 | */ |
49 | abstract protected function getTitleField(); |
50 | |
51 | /** |
52 | * Get the link target id (DB key) field name |
53 | * |
54 | * @return string |
55 | */ |
56 | abstract protected function getTargetIdField(); |
57 | |
58 | /** |
59 | * @return mixed |
60 | */ |
61 | abstract protected function getFromNamespaceField(); |
62 | |
63 | protected function getExistingFields() { |
64 | if ( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_OLD ) { |
65 | return [ |
66 | 'ns' => $this->getNamespaceField(), |
67 | 'title' => $this->getTitleField() |
68 | ]; |
69 | } |
70 | return [ |
71 | 'ns' => 'lt_namespace', |
72 | 'title' => 'lt_title', |
73 | ]; |
74 | } |
75 | |
76 | /** |
77 | * Get existing links as an associative array |
78 | * |
79 | * @return array |
80 | */ |
81 | private function getExistingLinks() { |
82 | if ( $this->existingLinks === null ) { |
83 | $this->existingLinks = []; |
84 | foreach ( $this->fetchExistingRows() as $row ) { |
85 | $this->existingLinks[$row->ns][$row->title] = 1; |
86 | } |
87 | } |
88 | |
89 | return $this->existingLinks; |
90 | } |
91 | |
92 | protected function fetchExistingRows(): IResultWrapper { |
93 | $queryBuilder = $this->getDB()->newSelectQueryBuilder() |
94 | ->select( $this->getExistingFields() ) |
95 | ->from( $this->getTableName() ) |
96 | ->where( $this->getFromConds() ); |
97 | |
98 | // This read is for updating, it's conceptually better to use the write config |
99 | if ( !( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_OLD ) ) { |
100 | $queryBuilder->join( 'linktarget', null, [ $this->getTargetIdField() . '=lt_id' ] ); |
101 | } |
102 | |
103 | return $queryBuilder |
104 | ->caller( __METHOD__ ) |
105 | ->fetchResultSet(); |
106 | } |
107 | |
108 | protected function getNewLinkIDs() { |
109 | foreach ( $this->newLinks as $ns => $links ) { |
110 | foreach ( $links as $dbk => $unused ) { |
111 | yield [ $ns, (string)$dbk ]; |
112 | } |
113 | } |
114 | } |
115 | |
116 | protected function getExistingLinkIDs() { |
117 | foreach ( $this->getExistingLinks() as $ns => $links ) { |
118 | foreach ( $links as $dbk => $unused ) { |
119 | yield [ $ns, (string)$dbk ]; |
120 | } |
121 | } |
122 | } |
123 | |
124 | protected function isExisting( $linkId ) { |
125 | [ $ns, $dbk ] = $linkId; |
126 | return isset( $this->getExistingLinks()[$ns][$dbk] ); |
127 | } |
128 | |
129 | protected function isInNewSet( $linkId ) { |
130 | [ $ns, $dbk ] = $linkId; |
131 | return isset( $this->newLinks[$ns][$dbk] ); |
132 | } |
133 | |
134 | protected function insertLink( $linkId ) { |
135 | $row = [ |
136 | $this->getFromNamespaceField() => $this->getSourcePage()->getNamespace(), |
137 | ]; |
138 | if ( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_OLD ) { |
139 | $row[$this->getNamespaceField()] = $linkId[0]; |
140 | $row[$this->getTitleField()] = $linkId[1]; |
141 | } |
142 | if ( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_NEW ) { |
143 | $row[$this->getTargetIdField()] = $this->linkTargetLookup->acquireLinkTargetId( |
144 | $this->makeTitle( $linkId ), |
145 | $this->getDB() |
146 | ); |
147 | } |
148 | $this->insertRow( $row ); |
149 | } |
150 | |
151 | protected function deleteLink( $linkId ) { |
152 | if ( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_OLD ) { |
153 | $this->deleteRow( [ |
154 | $this->getNamespaceField() => $linkId[0], |
155 | $this->getTitleField() => $linkId[1] |
156 | ] ); |
157 | } elseif ( $this->linksTargetNormalizationStage() & SCHEMA_COMPAT_WRITE_NEW ) { |
158 | $this->deleteRow( [ |
159 | $this->getTargetIdField() => $this->linkTargetLookup->acquireLinkTargetId( |
160 | $this->makeTitle( $linkId ), |
161 | $this->getDB() |
162 | ) |
163 | ] ); |
164 | } |
165 | } |
166 | |
167 | protected function needForcedLinkRefresh() { |
168 | return $this->isCrossNamespaceMove(); |
169 | } |
170 | |
171 | protected function makePageReferenceValue( $linkId ): PageReferenceValue { |
172 | return new PageReferenceValue( $linkId[0], $linkId[1], WikiAwareEntity::LOCAL ); |
173 | } |
174 | |
175 | protected function makeTitle( $linkId ): Title { |
176 | return Title::makeTitle( $linkId[0], $linkId[1] ); |
177 | } |
178 | |
179 | protected function deduplicateLinkIds( $linkIds ) { |
180 | $seen = []; |
181 | foreach ( $linkIds as $linkId ) { |
182 | if ( !isset( $seen[$linkId[0]][$linkId[1]] ) ) { |
183 | $seen[$linkId[0]][$linkId[1]] = true; |
184 | yield $linkId; |
185 | } |
186 | } |
187 | } |
188 | } |