Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 75 |
|
0.00% |
0 / 2 |
CRAP | |
0.00% |
0 / 1 |
PruneUnusedLinkTargetRows | |
0.00% |
0 / 75 |
|
0.00% |
0 / 2 |
90 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | |
3 | use MediaWiki\Maintenance\Maintenance; |
4 | |
5 | // @codeCoverageIgnoreStart |
6 | require_once __DIR__ . '/Maintenance.php'; |
7 | // @codeCoverageIgnoreEnd |
8 | |
9 | /** |
10 | * Maintenance script that cleans unused rows in linktarget table |
11 | * |
12 | * @ingroup Maintenance |
13 | * @since 1.39 |
14 | */ |
15 | class PruneUnusedLinkTargetRows extends Maintenance { |
16 | public function __construct() { |
17 | parent::__construct(); |
18 | $this->addDescription( |
19 | 'Clean unused rows in linktarget table' |
20 | ); |
21 | $this->addOption( |
22 | 'sleep', |
23 | 'Sleep time (in seconds) between every batch. Default: 0', |
24 | false, |
25 | true |
26 | ); |
27 | $this->addOption( 'dry', 'Dry run', false ); |
28 | $this->addOption( 'start', 'Start after this lt_id', false, true ); |
29 | $this->setBatchSize( 50 ); |
30 | } |
31 | |
32 | public function execute() { |
33 | $dbw = $this->getPrimaryDB(); |
34 | $dbr = $this->getReplicaDB(); |
35 | $maxLtId = (int)$dbr->newSelectQueryBuilder() |
36 | ->select( 'MAX(lt_id)' ) |
37 | ->from( 'linktarget' ) |
38 | ->caller( __METHOD__ ) |
39 | ->fetchField(); |
40 | // To avoid race condition of newly added linktarget rows |
41 | // being deleted before getting a chance to be used, let's ignore the newest ones. |
42 | $maxLtId = min( [ $maxLtId - 1, (int)( $maxLtId * 0.99 ) ] ); |
43 | |
44 | $ltCounter = (int)$this->getOption( 'start', 0 ); |
45 | |
46 | $this->output( "Deleting unused linktarget rows...\n" ); |
47 | $deleted = 0; |
48 | $linksMigration = $this->getServiceContainer()->getLinksMigration(); |
49 | while ( $ltCounter < $maxLtId ) { |
50 | $batchMaxLtId = min( $ltCounter + $this->getBatchSize(), $maxLtId ) + 1; |
51 | $this->output( "Checking lt_id between $ltCounter and $batchMaxLtId...\n" ); |
52 | $queryBuilder = $dbr->newSelectQueryBuilder() |
53 | ->select( [ 'lt_id' ] ) |
54 | ->from( 'linktarget' ); |
55 | $queryBuilder->where( [ |
56 | $dbr->expr( 'lt_id', '<', $batchMaxLtId ), |
57 | $dbr->expr( 'lt_id', '>', $ltCounter ) |
58 | ] ); |
59 | foreach ( $linksMigration::$mapping as $table => $tableData ) { |
60 | $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' ); |
61 | $queryBuilder->andWhere( [ |
62 | $tableData['target_id'] => null |
63 | ] ); |
64 | } |
65 | $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues(); |
66 | if ( !$ltIdsToDelete ) { |
67 | $ltCounter += $this->getBatchSize(); |
68 | continue; |
69 | } |
70 | |
71 | // Run against primary as well with a faster query plan, just to be safe. |
72 | // Also having a bit of time in between helps in cases of immediate removal and insertion of use. |
73 | $queryBuilder = $dbr->newSelectQueryBuilder() |
74 | ->select( [ 'lt_id' ] ) |
75 | ->from( 'linktarget' ) |
76 | ->where( [ |
77 | 'lt_id' => $ltIdsToDelete, |
78 | ] ); |
79 | foreach ( $linksMigration::$mapping as $table => $tableData ) { |
80 | $queryBuilder->leftJoin( $table, null, $tableData['target_id'] . '=lt_id' ); |
81 | $queryBuilder->andWhere( [ |
82 | $tableData['target_id'] => null |
83 | ] ); |
84 | } |
85 | $ltIdsToDelete = $queryBuilder->caller( __METHOD__ )->fetchFieldValues(); |
86 | if ( !$ltIdsToDelete ) { |
87 | $ltCounter += $this->getBatchSize(); |
88 | continue; |
89 | } |
90 | |
91 | if ( !$this->getOption( 'dry' ) ) { |
92 | $dbw->newDeleteQueryBuilder() |
93 | ->deleteFrom( 'linktarget' ) |
94 | ->where( [ 'lt_id' => $ltIdsToDelete ] ) |
95 | ->caller( __METHOD__ )->execute(); |
96 | } |
97 | $deleted += count( $ltIdsToDelete ); |
98 | $ltCounter += $this->getBatchSize(); |
99 | |
100 | // Sleep between batches for replication to catch up |
101 | $this->waitForReplication(); |
102 | $sleep = (int)$this->getOption( 'sleep', 0 ); |
103 | if ( $sleep > 0 ) { |
104 | sleep( $sleep ); |
105 | } |
106 | } |
107 | |
108 | $this->output( |
109 | "Completed clean up linktarget table, " |
110 | . "$deleted rows deleted.\n" |
111 | ); |
112 | |
113 | return true; |
114 | } |
115 | |
116 | } |
117 | |
118 | // @codeCoverageIgnoreStart |
119 | $maintClass = PruneUnusedLinkTargetRows::class; |
120 | require_once RUN_MAINTENANCE_IF_MAIN; |
121 | // @codeCoverageIgnoreEnd |