Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
CloneDatabase
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 5
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 useTemporaryTables
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cloneTableStructure
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 destroy
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 changePrefix
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Helper class for making a copy of the database, mostly for unit testing.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup Database
8 */
9
10namespace MediaWiki\DB;
11
12use InvalidArgumentException;
13use LogicException;
14use MediaWiki\MediaWikiServices;
15use RuntimeException;
16use Wikimedia\Rdbms\IMaintainableDatabase;
17
18class CloneDatabase {
19    /** Table prefix for cloning */
20    private string $newTablePrefix;
21
22    /** Current table prefix */
23    private string $oldTablePrefix;
24
25    /** List of tables to be cloned */
26    private array $tablesToClone;
27
28    /** Should we DROP tables containing the new names? */
29    private bool $dropCurrentTables;
30
31    /** Whether to use temporary tables or not */
32    private bool $useTemporaryTables = true;
33
34    private IMaintainableDatabase $db;
35
36    /**
37     * @param IMaintainableDatabase $db A database subclass
38     * @param array $tablesToClone An array of tables to clone, unprefixed
39     * @param string $newTablePrefix Prefix to assign to the tables
40     * @param string|null $oldTablePrefix Prefix on current tables, if not $wgDBprefix
41     * @param bool $dropCurrentTables
42     */
43    public function __construct(
44        IMaintainableDatabase $db,
45        array $tablesToClone,
46        string $newTablePrefix,
47        ?string $oldTablePrefix = null,
48        bool $dropCurrentTables = true
49    ) {
50        if ( !$tablesToClone ) {
51            throw new InvalidArgumentException( 'Empty list of tables to clone' );
52        }
53        $this->db = $db;
54        $this->tablesToClone = $tablesToClone;
55        $this->newTablePrefix = $newTablePrefix;
56        $this->oldTablePrefix = $oldTablePrefix ?? $this->db->tablePrefix();
57        $this->dropCurrentTables = $dropCurrentTables;
58    }
59
60    /**
61     * Set whether to use temporary tables or not
62     * @param bool $u Use temporary tables when cloning the structure
63     */
64    public function useTemporaryTables( bool $u = true ): void {
65        $this->useTemporaryTables = $u;
66    }
67
68    public function cloneTableStructure(): void {
69        global $wgSharedTables, $wgSharedDB;
70        foreach ( $this->tablesToClone as $tbl ) {
71            if ( $wgSharedDB && in_array( $tbl, $wgSharedTables, true ) ) {
72                // Shared tables don't work properly when cloning due to
73                // how prefixes are handled (T67654)
74                throw new RuntimeException( "Cannot clone shared table $tbl." );
75            }
76            # Clean up from previous aborted run.  So that table escaping
77            # works correctly across DB engines, we need to change the pre-
78            # fix back and forth so tableName() works right.
79
80            $this->db->tablePrefix( $this->oldTablePrefix );
81            $oldTableName = $this->db->tableName( $tbl, 'raw' );
82
83            $this->db->tablePrefix( $this->newTablePrefix );
84            $newTableName = $this->db->tableName( $tbl, 'raw' );
85
86            // Postgres: Temp tables are automatically deleted upon end of session
87            //           Same Temp table name hides existing table for current session
88            if ( $this->dropCurrentTables ) {
89                if ( $oldTableName === $newTableName ) {
90                    // Last ditch check to avoid data loss
91                    throw new LogicException( "Not dropping new table, as '$newTableName'"
92                        . " is name of both the old and the new table." );
93                }
94                $this->db->dropTable( $tbl, __METHOD__ );
95                // Dropping the oldTable because the prefix was changed
96            }
97
98            # Create new table
99            $this->db->duplicateTableStructure(
100                $oldTableName, $newTableName, $this->useTemporaryTables, __METHOD__ );
101        }
102    }
103
104    /**
105     * Change the prefix back to the original.
106     * @param bool $dropTables Optionally drop the tables we created
107     */
108    public function destroy( bool $dropTables = false ): void {
109        if ( $dropTables ) {
110            $this->db->tablePrefix( $this->newTablePrefix );
111            foreach ( $this->tablesToClone as $tbl ) {
112                $this->db->dropTable( $tbl, __METHOD__ );
113            }
114        }
115        $this->db->tablePrefix( $this->oldTablePrefix );
116    }
117
118    /**
119     * Change the table prefix on all open DB connections
120     *
121     * @param string $prefix
122     * @return void
123     */
124    public static function changePrefix( string $prefix ): void {
125        global $wgDBprefix, $wgDBname;
126
127        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
128        $lbFactory->setLocalDomainPrefix( $prefix );
129
130        $aliases = [
131            $wgDBname => $lbFactory->getLocalDomainID()
132        ];
133        $lbFactory->setDomainAliases( $aliases );
134        foreach ( $lbFactory->getAllLBs() as $lb ) {
135            $lb->setDomainAliases( $aliases );
136        }
137
138        $wgDBprefix = $prefix;
139    }
140}
141
142/** @deprecated class alias since 1.46 */
143class_alias( CloneDatabase::class, 'CloneDatabase' );