Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
87.65% |
71 / 81 |
|
80.00% |
24 / 30 |
CRAP | |
0.00% |
0 / 1 |
| LinksTable | |
87.65% |
71 / 81 |
|
80.00% |
24 / 30 |
52.34 | |
0.00% |
0 / 1 |
| injectBaseDependencies | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| setTransactionTicket | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setRevision | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setMoveDetails | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setParserOutput | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getTableName | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getFromField | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getExistingFields | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getNewLinkIDs | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getExistingLinkIDs | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| isExisting | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| isInNewSet | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| insertLink | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| deleteLink | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| needForcedLinkRefresh | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getDB | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getReplicaDB | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getLBFactory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getSourcePageId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getSourcePage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isMove | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| isCrossNamespaceMove | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| getMovedPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getBatchSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getTransactionTicket | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRevision | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getFromConds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| fetchExistingRows | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| update | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
7 | |||
| insertRow | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| deleteRow | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| beforeLock | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| startUpdate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| finishUpdate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| doWrites | |
90.48% |
19 / 21 |
|
0.00% |
0 / 1 |
5.02 | |||
| setStrictTestMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getInsertOptions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getLinkIDs | |
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
21.52 | |||
| linksTargetNormalizationStage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| virtualDomain | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace MediaWiki\Deferred\LinksUpdate; |
| 4 | |
| 5 | use InvalidArgumentException; |
| 6 | use MediaWiki\Linker\LinkTargetLookup; |
| 7 | use MediaWiki\Page\PageIdentity; |
| 8 | use MediaWiki\Page\PageReference; |
| 9 | use MediaWiki\Parser\ParserOutput; |
| 10 | use MediaWiki\Revision\RevisionRecord; |
| 11 | use Wikimedia\Rdbms\IDatabase; |
| 12 | use Wikimedia\Rdbms\IReadableDatabase; |
| 13 | use Wikimedia\Rdbms\IResultWrapper; |
| 14 | use Wikimedia\Rdbms\LBFactory; |
| 15 | |
| 16 | /** |
| 17 | * The base class for classes which update a single link table. |
| 18 | * |
| 19 | * A LinksTable object is a container for new and existing link sets outbound |
| 20 | * from a single page, and an abstraction of the associated DB schema. The |
| 21 | * object stores state related to an update of the outbound links of a page. |
| 22 | * |
| 23 | * Explanation of link ID concept |
| 24 | * ------------------------------ |
| 25 | * |
| 26 | * Link IDs identify a link in the new or old state, or in the change arrays. |
| 27 | * They are opaque to the base class and are type-hinted here as mixed. |
| 28 | * |
| 29 | * Conventionally, the link ID is string|string[] and contains the link target |
| 30 | * fields. |
| 31 | * |
| 32 | * The link ID should contain enough information so that the base class can |
| 33 | * tell whether an existing link is in the new set, or vice versa, for the |
| 34 | * purposes of incremental updates. If a change to a field would cause a DB |
| 35 | * update, the field should be in the link ID. |
| 36 | * |
| 37 | * For example, a change to cl_timestamp does not trigger an update, so |
| 38 | * cl_timestamp is not in the link ID. |
| 39 | * |
| 40 | * @stable to extend |
| 41 | * @since 1.38 |
| 42 | */ |
| 43 | abstract class LinksTable { |
| 44 | /** Link type: Inserted (added) links */ |
| 45 | public const INSERTED = 1; |
| 46 | |
| 47 | /** Link type: Deleted (removed) links */ |
| 48 | public const DELETED = 2; |
| 49 | |
| 50 | /** Link type: Changed (inserted or removed) links */ |
| 51 | public const CHANGED = 3; |
| 52 | |
| 53 | /** Link type: existing/old links */ |
| 54 | public const OLD = 4; |
| 55 | |
| 56 | /** Link type: new links (from the ParserOutput) */ |
| 57 | public const NEW = 5; |
| 58 | |
| 59 | /** |
| 60 | * Rows to delete. An array of associative arrays, each associative array |
| 61 | * being the conditions for a delete query. Common conditions should be |
| 62 | * leftmost in the associative array so that they can be factored out. |
| 63 | * |
| 64 | * @var array |
| 65 | */ |
| 66 | protected $rowsToDelete = []; |
| 67 | |
| 68 | /** |
| 69 | * Rows to insert. An array of associative arrays, each associative array |
| 70 | * mapping field names to values. |
| 71 | * |
| 72 | * @var array |
| 73 | */ |
| 74 | protected $rowsToInsert = []; |
| 75 | |
| 76 | /** @var array Link IDs for inserted links */ |
| 77 | protected $insertedLinks = []; |
| 78 | |
| 79 | /** @var array Link IDs for deleted links */ |
| 80 | protected $deletedLinks = []; |
| 81 | |
| 82 | /** @var LBFactory */ |
| 83 | private $lbFactory; |
| 84 | |
| 85 | /** @var LinkTargetLookup */ |
| 86 | protected $linkTargetLookup; |
| 87 | |
| 88 | /** @var IDatabase */ |
| 89 | private $db; |
| 90 | |
| 91 | /** @var IReadableDatabase */ |
| 92 | private $replicaDb; |
| 93 | |
| 94 | /** @var PageIdentity */ |
| 95 | private $sourcePage; |
| 96 | |
| 97 | /** @var PageReference|null */ |
| 98 | private $movedPage; |
| 99 | |
| 100 | /** @var int */ |
| 101 | private $batchSize; |
| 102 | |
| 103 | /** @var mixed */ |
| 104 | private $ticket; |
| 105 | |
| 106 | /** @var RevisionRecord */ |
| 107 | private $revision; |
| 108 | |
| 109 | /** @var bool */ |
| 110 | protected $strictTestMode; |
| 111 | |
| 112 | /** |
| 113 | * This is called by the factory to inject dependencies for the base class. |
| 114 | * This is used instead of the constructor so that changes can be made to |
| 115 | * the injected parameters without breaking the subclass constructors. |
| 116 | * |
| 117 | * @param LBFactory $lbFactory |
| 118 | * @param LinkTargetLookup $linkTargetLookup |
| 119 | * @param PageIdentity $sourcePage |
| 120 | * @param int $batchSize |
| 121 | */ |
| 122 | final public function injectBaseDependencies( |
| 123 | LBFactory $lbFactory, |
| 124 | LinkTargetLookup $linkTargetLookup, |
| 125 | PageIdentity $sourcePage, |
| 126 | $batchSize |
| 127 | ) { |
| 128 | $this->lbFactory = $lbFactory; |
| 129 | $this->db = $this->lbFactory->getPrimaryDatabase( $this->virtualDomain() ); |
| 130 | $this->replicaDb = $this->lbFactory->getReplicaDatabase( $this->virtualDomain() ); |
| 131 | $this->sourcePage = $sourcePage; |
| 132 | $this->batchSize = $batchSize; |
| 133 | $this->linkTargetLookup = $linkTargetLookup; |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Set the empty transaction ticket |
| 138 | * |
| 139 | * @param mixed $ticket |
| 140 | */ |
| 141 | public function setTransactionTicket( $ticket ) { |
| 142 | $this->ticket = $ticket; |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Set the revision associated with the edit. |
| 147 | */ |
| 148 | public function setRevision( RevisionRecord $revision ) { |
| 149 | $this->revision = $revision; |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Notify the object that the operation is a page move, and set the |
| 154 | * original title. |
| 155 | */ |
| 156 | public function setMoveDetails( PageReference $movedPage ) { |
| 157 | $this->movedPage = $movedPage; |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Subclasses should implement this to extract the data they need from the |
| 162 | * ParserOutput. |
| 163 | * |
| 164 | * To support a future refactor of LinksDeletionUpdate, if this method is |
| 165 | * not called, the subclass should assume that the new state is empty. |
| 166 | */ |
| 167 | abstract public function setParserOutput( ParserOutput $parserOutput ); |
| 168 | |
| 169 | /** |
| 170 | * Get the table name. |
| 171 | * |
| 172 | * @return string |
| 173 | */ |
| 174 | abstract protected function getTableName(); |
| 175 | |
| 176 | /** |
| 177 | * Get the name of the field which links to page_id. |
| 178 | * |
| 179 | * @return string |
| 180 | */ |
| 181 | abstract protected function getFromField(); |
| 182 | |
| 183 | /** |
| 184 | * Get the fields to be used in fetchExistingRows(). Note that |
| 185 | * fetchExistingRows() is just a helper for subclasses. The value returned |
| 186 | * here is effectively private to the subclass. |
| 187 | * |
| 188 | * @return array |
| 189 | */ |
| 190 | abstract protected function getExistingFields(); |
| 191 | |
| 192 | /** |
| 193 | * Get an array (or iterator) of link IDs for the new state. |
| 194 | * |
| 195 | * See the LinksTable doc comment for an explanation of link IDs. |
| 196 | * |
| 197 | * @return iterable<mixed> |
| 198 | */ |
| 199 | abstract protected function getNewLinkIDs(); |
| 200 | |
| 201 | /** |
| 202 | * Get an array (or iterator) of link IDs for the existing state. The |
| 203 | * subclass should load the data from the database. There is |
| 204 | * fetchExistingRows() to make this easier but the subclass is responsible |
| 205 | * for caching. |
| 206 | * |
| 207 | * See the LinksTable doc comment for an explanation of link IDs. |
| 208 | * |
| 209 | * @return iterable<mixed> |
| 210 | */ |
| 211 | abstract protected function getExistingLinkIDs(); |
| 212 | |
| 213 | /** |
| 214 | * Determine whether a link (from the new set) is in the existing set. |
| 215 | * |
| 216 | * @param mixed $linkId |
| 217 | * @return bool |
| 218 | */ |
| 219 | abstract protected function isExisting( $linkId ); |
| 220 | |
| 221 | /** |
| 222 | * Determine whether a link (from the existing set) is in the new set. |
| 223 | * |
| 224 | * @param mixed $linkId |
| 225 | * @return bool |
| 226 | */ |
| 227 | abstract protected function isInNewSet( $linkId ); |
| 228 | |
| 229 | /** |
| 230 | * Insert a link identified by ID. The subclass is expected to queue the |
| 231 | * insertion by calling insertRow(). |
| 232 | * |
| 233 | * @param mixed $linkId |
| 234 | */ |
| 235 | abstract protected function insertLink( $linkId ); |
| 236 | |
| 237 | /** |
| 238 | * Delete a link identified by ID. The subclass is expected to queue the |
| 239 | * deletion by calling deleteRow(). |
| 240 | * |
| 241 | * @param mixed $linkId |
| 242 | */ |
| 243 | abstract protected function deleteLink( $linkId ); |
| 244 | |
| 245 | /** |
| 246 | * Subclasses can override this to return true in order to force |
| 247 | * reinsertion of all the links due to some property of the link |
| 248 | * changing for reasons not represented by the link ID. |
| 249 | * |
| 250 | * @return bool |
| 251 | */ |
| 252 | protected function needForcedLinkRefresh() { |
| 253 | return false; |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * @stable to override |
| 258 | * @return IDatabase |
| 259 | */ |
| 260 | protected function getDB(): IDatabase { |
| 261 | return $this->db; |
| 262 | } |
| 263 | |
| 264 | protected function getReplicaDB(): IReadableDatabase { |
| 265 | return $this->replicaDb; |
| 266 | } |
| 267 | |
| 268 | protected function getLBFactory(): LBFactory { |
| 269 | return $this->lbFactory; |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Get the page_id of the source page |
| 274 | */ |
| 275 | protected function getSourcePageId(): int { |
| 276 | return $this->sourcePage->getId(); |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * Get the source page, i.e. the page which is being updated and is the |
| 281 | * source of links. |
| 282 | */ |
| 283 | protected function getSourcePage(): PageIdentity { |
| 284 | return $this->sourcePage; |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * Determine whether the page was moved |
| 289 | * |
| 290 | * @return bool |
| 291 | */ |
| 292 | protected function isMove() { |
| 293 | return $this->movedPage !== null; |
| 294 | } |
| 295 | |
| 296 | /** |
| 297 | * Determine whether the page was moved to a different namespace. |
| 298 | * |
| 299 | * @return bool |
| 300 | */ |
| 301 | protected function isCrossNamespaceMove() { |
| 302 | return $this->movedPage !== null |
| 303 | && $this->sourcePage->getNamespace() !== $this->movedPage->getNamespace(); |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Assuming the page was moved, get the original page title before the move. |
| 308 | * This will throw an exception if the page wasn't moved. |
| 309 | */ |
| 310 | protected function getMovedPage(): PageReference { |
| 311 | return $this->movedPage; |
| 312 | } |
| 313 | |
| 314 | /** |
| 315 | * Get the maximum number of rows to update in a batch. |
| 316 | */ |
| 317 | protected function getBatchSize(): int { |
| 318 | return $this->batchSize; |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | * Get the empty transaction ticket, or null if there is none. |
| 323 | * |
| 324 | * @return mixed |
| 325 | */ |
| 326 | protected function getTransactionTicket() { |
| 327 | return $this->ticket; |
| 328 | } |
| 329 | |
| 330 | /** |
| 331 | * Get the RevisionRecord of the new revision, if the LinksUpdate caller |
| 332 | * injected one. |
| 333 | * |
| 334 | * @return RevisionRecord|null |
| 335 | */ |
| 336 | protected function getRevision(): ?RevisionRecord { |
| 337 | return $this->revision; |
| 338 | } |
| 339 | |
| 340 | /** |
| 341 | * Get field=>value associative array for the from field(s) |
| 342 | * |
| 343 | * @stable to override |
| 344 | * @return array |
| 345 | */ |
| 346 | protected function getFromConds() { |
| 347 | return [ $this->getFromField() => $this->getSourcePageId() ]; |
| 348 | } |
| 349 | |
| 350 | /** |
| 351 | * Do a select query to fetch the existing rows. This is a helper for |
| 352 | * subclasses. |
| 353 | */ |
| 354 | protected function fetchExistingRows(): IResultWrapper { |
| 355 | return $this->getReplicaDB()->newSelectQueryBuilder() |
| 356 | ->select( $this->getExistingFields() ) |
| 357 | ->from( $this->getTableName() ) |
| 358 | ->where( $this->getFromConds() ) |
| 359 | ->caller( __METHOD__ ) |
| 360 | ->fetchResultSet(); |
| 361 | } |
| 362 | |
| 363 | /** |
| 364 | * Execute an edit/delete update |
| 365 | */ |
| 366 | final public function update() { |
| 367 | $this->startUpdate(); |
| 368 | $force = $this->needForcedLinkRefresh(); |
| 369 | foreach ( $this->getNewLinkIDs() as $link ) { |
| 370 | if ( $force || !$this->isExisting( $link ) ) { |
| 371 | $this->insertLink( $link ); |
| 372 | $this->insertedLinks[] = $link; |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | foreach ( $this->getExistingLinkIDs() as $link ) { |
| 377 | if ( $force || !$this->isInNewSet( $link ) ) { |
| 378 | $this->deleteLink( $link ); |
| 379 | $this->deletedLinks[] = $link; |
| 380 | } |
| 381 | } |
| 382 | $this->doWrites(); |
| 383 | $this->finishUpdate(); |
| 384 | } |
| 385 | |
| 386 | /** |
| 387 | * Queue a row for insertion. Subclasses are expected to call this from |
| 388 | * insertLink(). The "from" field should not be included in the row. |
| 389 | * |
| 390 | * @param array $row Associative array mapping fields to values. |
| 391 | */ |
| 392 | protected function insertRow( $row ) { |
| 393 | $row += $this->getFromConds(); |
| 394 | $this->rowsToInsert[] = $row; |
| 395 | } |
| 396 | |
| 397 | /** |
| 398 | * Queue a deletion operation. Subclasses are expected to call this from |
| 399 | * deleteLink(). The "from" field does not need to be included in the |
| 400 | * conditions. |
| 401 | * |
| 402 | * Most often, the conditions match a single row, but this is not required. |
| 403 | * |
| 404 | * @param array $conds Associative array mapping fields to values, |
| 405 | * specifying the conditions for a delete query. |
| 406 | */ |
| 407 | protected function deleteRow( $conds ) { |
| 408 | // Put the "from" field leftmost, so it can be factored out |
| 409 | $conds = $this->getFromConds() + $conds; |
| 410 | $this->rowsToDelete[] = $conds; |
| 411 | } |
| 412 | |
| 413 | /** |
| 414 | * Subclasses can override this to do any necessary setup before the lock |
| 415 | * is acquired. |
| 416 | * |
| 417 | * @stable to override |
| 418 | */ |
| 419 | public function beforeLock() { |
| 420 | } |
| 421 | |
| 422 | /** |
| 423 | * Subclasses can override this to do any necessary setup before individual |
| 424 | * write operations begin. |
| 425 | * |
| 426 | * @stable to override |
| 427 | */ |
| 428 | protected function startUpdate() { |
| 429 | } |
| 430 | |
| 431 | /** |
| 432 | * Subclasses can override this to do any updates associated with their |
| 433 | * link data, for example dispatching HTML update jobs. |
| 434 | * |
| 435 | * @stable to override |
| 436 | */ |
| 437 | protected function finishUpdate() { |
| 438 | } |
| 439 | |
| 440 | /** |
| 441 | * Do the common DB operations |
| 442 | */ |
| 443 | protected function doWrites() { |
| 444 | $db = $this->getDB(); |
| 445 | $table = $this->getTableName(); |
| 446 | $batchSize = $this->getBatchSize(); |
| 447 | $ticket = $this->getTransactionTicket(); |
| 448 | |
| 449 | $deleteBatches = array_chunk( $this->rowsToDelete, $batchSize ); |
| 450 | foreach ( $deleteBatches as $chunk ) { |
| 451 | $db->newDeleteQueryBuilder() |
| 452 | ->deleteFrom( $table ) |
| 453 | ->where( $db->factorConds( $chunk ) ) |
| 454 | ->caller( __METHOD__ )->execute(); |
| 455 | if ( count( $deleteBatches ) > 1 ) { |
| 456 | $this->lbFactory->commitAndWaitForReplication( __METHOD__, $ticket ); |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | $insertBatches = array_chunk( $this->rowsToInsert, $batchSize ); |
| 461 | foreach ( $insertBatches as $insertBatch ) { |
| 462 | $db->newInsertQueryBuilder() |
| 463 | ->options( $this->getInsertOptions() ) |
| 464 | ->insertInto( $table ) |
| 465 | ->rows( $insertBatch ) |
| 466 | ->caller( __METHOD__ )->execute(); |
| 467 | if ( count( $insertBatches ) > 1 ) { |
| 468 | $this->lbFactory->commitAndWaitForReplication( __METHOD__, $ticket ); |
| 469 | } |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | /** |
| 474 | * Omit conflict resolution options from the insert query so that testing |
| 475 | * can confirm that the incremental update logic was correct. |
| 476 | * |
| 477 | * @param bool $mode |
| 478 | */ |
| 479 | public function setStrictTestMode( $mode = true ) { |
| 480 | $this->strictTestMode = $mode; |
| 481 | } |
| 482 | |
| 483 | /** |
| 484 | * Get the options for the insert queries |
| 485 | * |
| 486 | * @return array |
| 487 | */ |
| 488 | protected function getInsertOptions() { |
| 489 | if ( $this->strictTestMode ) { |
| 490 | return []; |
| 491 | } else { |
| 492 | return [ 'IGNORE' ]; |
| 493 | } |
| 494 | } |
| 495 | |
| 496 | /** |
| 497 | * Get an array or iterator of link IDs of a given type. Some subclasses |
| 498 | * use this to provide typed data to callers. This is not public because |
| 499 | * link IDs are a private concept. |
| 500 | * |
| 501 | * @param int $setType One of the class constants: self::INSERTED, self::DELETED, |
| 502 | * self::CHANGED, self::OLD or self::NEW. |
| 503 | * @return iterable<mixed> |
| 504 | */ |
| 505 | protected function getLinkIDs( $setType ) { |
| 506 | switch ( $setType ) { |
| 507 | case self::INSERTED: |
| 508 | return $this->insertedLinks; |
| 509 | |
| 510 | case self::DELETED: |
| 511 | return $this->deletedLinks; |
| 512 | |
| 513 | case self::CHANGED: |
| 514 | return array_merge( $this->insertedLinks, $this->deletedLinks ); |
| 515 | |
| 516 | case self::OLD: |
| 517 | return $this->getExistingLinkIDs(); |
| 518 | |
| 519 | case self::NEW: |
| 520 | return $this->getNewLinkIDs(); |
| 521 | |
| 522 | default: |
| 523 | throw new InvalidArgumentException( __METHOD__ . ": Unknown link type" ); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | /** |
| 528 | * Normalization stage of the links table (see T222224) |
| 529 | */ |
| 530 | protected function linksTargetNormalizationStage(): int { |
| 531 | return SCHEMA_COMPAT_OLD; |
| 532 | } |
| 533 | |
| 534 | /** |
| 535 | * What virtual domain should be used to read/write from the table |
| 536 | * @return string|bool |
| 537 | */ |
| 538 | protected function virtualDomain() { |
| 539 | return false; |
| 540 | } |
| 541 | } |