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