Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 37 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
| CloneDatabase | |
0.00% |
0 / 36 |
|
0.00% |
0 / 5 |
210 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| useTemporaryTables | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| cloneTableStructure | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
| destroy | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
| changePrefix | |
0.00% |
0 / 9 |
|
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 | |
| 10 | namespace MediaWiki\DB; |
| 11 | |
| 12 | use InvalidArgumentException; |
| 13 | use LogicException; |
| 14 | use MediaWiki\MediaWikiServices; |
| 15 | use RuntimeException; |
| 16 | use Wikimedia\Rdbms\IMaintainableDatabase; |
| 17 | |
| 18 | class 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 */ |
| 143 | class_alias( CloneDatabase::class, 'CloneDatabase' ); |