Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 84 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
| PostgresInstaller | |
0.00% |
0 / 84 |
|
0.00% |
0 / 11 |
650 | |
0.00% |
0 / 1 |
| getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| isCompiled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getConnectForm | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getSettingsForm | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| openConnectionWithParams | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
| openConnection | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
| changeConnTypeFromSchemaToTables | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
| openConnectionToAnyDB | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
42 | |||
| getLocalSettings | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| preUpgrade | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| getGlobalDefaults | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * PostgreSQL-specific installer. |
| 4 | * |
| 5 | * @license GPL-2.0-or-later |
| 6 | * @file |
| 7 | * @ingroup Installer |
| 8 | */ |
| 9 | |
| 10 | namespace MediaWiki\Installer; |
| 11 | |
| 12 | use InvalidArgumentException; |
| 13 | use MediaWiki\MediaWikiServices; |
| 14 | use Wikimedia\Rdbms\DatabaseFactory; |
| 15 | use Wikimedia\Rdbms\DatabasePostgres; |
| 16 | use Wikimedia\Rdbms\DBConnectionError; |
| 17 | use Wikimedia\Rdbms\IMaintainableDatabase; |
| 18 | |
| 19 | /** |
| 20 | * Class for setting up the MediaWiki database using Postgres. |
| 21 | * |
| 22 | * @ingroup Installer |
| 23 | * @since 1.17 |
| 24 | */ |
| 25 | class PostgresInstaller extends DatabaseInstaller { |
| 26 | |
| 27 | /** @inheritDoc */ |
| 28 | protected $globalNames = [ |
| 29 | 'wgDBserver', |
| 30 | 'wgDBport', |
| 31 | 'wgDBname', |
| 32 | 'wgDBuser', |
| 33 | 'wgDBpassword', |
| 34 | 'wgDBssl', |
| 35 | 'wgDBmwschema', |
| 36 | ]; |
| 37 | |
| 38 | /** @inheritDoc */ |
| 39 | protected $internalDefaults = [ |
| 40 | '_InstallUser' => 'postgres', |
| 41 | ]; |
| 42 | |
| 43 | /** @inheritDoc */ |
| 44 | public static $minimumVersion = '10'; |
| 45 | /** @inheritDoc */ |
| 46 | protected static $notMinimumVersionMessage = 'config-postgres-old'; |
| 47 | |
| 48 | /** @inheritDoc */ |
| 49 | public function getName() { |
| 50 | return 'postgres'; |
| 51 | } |
| 52 | |
| 53 | /** @inheritDoc */ |
| 54 | public function isCompiled() { |
| 55 | return self::checkExtension( 'pgsql' ); |
| 56 | } |
| 57 | |
| 58 | public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm { |
| 59 | return new PostgresConnectForm( $webInstaller, $this ); |
| 60 | } |
| 61 | |
| 62 | public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm { |
| 63 | return new PostgresSettingsForm( $webInstaller, $this ); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Open a PG connection with given parameters |
| 68 | * @param string $user User name |
| 69 | * @param string $password |
| 70 | * @param string $dbName Database name |
| 71 | * @param string $schema Database schema |
| 72 | * @return ConnectionStatus |
| 73 | */ |
| 74 | protected function openConnectionWithParams( $user, $password, $dbName, $schema ) { |
| 75 | $status = new ConnectionStatus; |
| 76 | try { |
| 77 | $db = MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'postgres', [ |
| 78 | 'host' => $this->getVar( 'wgDBserver' ), |
| 79 | 'port' => $this->getVar( 'wgDBport' ), |
| 80 | 'user' => $user, |
| 81 | 'password' => $password, |
| 82 | 'ssl' => $this->getVar( 'wgDBssl' ), |
| 83 | 'dbname' => $dbName, |
| 84 | 'schema' => $schema, |
| 85 | ] ); |
| 86 | $status->setDB( $db ); |
| 87 | } catch ( DBConnectionError $e ) { |
| 88 | $status->fatal( 'config-connection-error', $e->getMessage() ); |
| 89 | } |
| 90 | |
| 91 | return $status; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Get a connection of a specific PostgreSQL-specific type. Connections |
| 96 | * of a given type are cached. |
| 97 | * |
| 98 | * PostgreSQL lacks cross-database operations, so after the new database is |
| 99 | * created, you need to make a separate connection to connect to that |
| 100 | * database and add tables to it. |
| 101 | * |
| 102 | * New tables are owned by the user that creates them, and MediaWiki's |
| 103 | * PostgreSQL support has always assumed that the table owner will be |
| 104 | * $wgDBuser. So before we create new tables, we either need to either |
| 105 | * connect as the other user or to execute a SET ROLE command. Using a |
| 106 | * separate connection for this allows us to avoid accidental cross-module |
| 107 | * dependencies. |
| 108 | * |
| 109 | * @param string $type The type of connection to get: |
| 110 | * - self::CONN_CREATE_DATABASE: A connection for creating DBs, suitable for pre- |
| 111 | * installation. |
| 112 | * - self::CONN_CREATE_SCHEMA: A connection to the new DB, for creating schemas and |
| 113 | * other similar objects in the new DB. |
| 114 | * - self::CONN_CREATE_TABLES: A connection with a role suitable for creating tables. |
| 115 | * @return ConnectionStatus On success, a connection object will be in the value member. |
| 116 | */ |
| 117 | protected function openConnection( string $type ) { |
| 118 | switch ( $type ) { |
| 119 | case self::CONN_CREATE_DATABASE: |
| 120 | return $this->openConnectionToAnyDB( |
| 121 | $this->getVar( '_InstallUser' ), |
| 122 | $this->getVar( '_InstallPassword' ) ); |
| 123 | case self::CONN_CREATE_SCHEMA: |
| 124 | return $this->openConnectionWithParams( |
| 125 | $this->getVar( '_InstallUser' ), |
| 126 | $this->getVar( '_InstallPassword' ), |
| 127 | $this->getVar( 'wgDBname' ), |
| 128 | $this->getVar( 'wgDBmwschema' ) ); |
| 129 | case self::CONN_CREATE_TABLES: |
| 130 | $status = $this->openConnection( self::CONN_CREATE_SCHEMA ); |
| 131 | if ( $status->isOK() ) { |
| 132 | $status->merge( $this->changeConnTypeFromSchemaToTables( $status->getDB() ) ); |
| 133 | } |
| 134 | |
| 135 | return $status; |
| 136 | default: |
| 137 | throw new InvalidArgumentException( "Invalid connection type: \"$type\"" ); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | /** @inheritDoc */ |
| 142 | protected function changeConnTypeFromSchemaToTables( IMaintainableDatabase $conn ) { |
| 143 | if ( !( $conn instanceof DatabasePostgres ) ) { |
| 144 | throw new InvalidArgumentException( 'Invalid connection type' ); |
| 145 | } |
| 146 | $status = new ConnectionStatus( $conn ); |
| 147 | $schema = $this->getVar( 'wgDBmwschema' ); |
| 148 | if ( !$conn->schemaExists( $schema ) ) { |
| 149 | $status->fatal( 'config-install-pg-schema-not-exist' ); |
| 150 | return $status; |
| 151 | } |
| 152 | $conn->determineCoreSchema( $schema ); |
| 153 | |
| 154 | $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); |
| 155 | $conn->query( "SET ROLE $safeRole", __METHOD__ ); |
| 156 | return $status; |
| 157 | } |
| 158 | |
| 159 | public function openConnectionToAnyDB( string $user, ?string $password ): ConnectionStatus { |
| 160 | $dbs = [ |
| 161 | 'template1', |
| 162 | 'postgres', |
| 163 | ]; |
| 164 | if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) { |
| 165 | array_unshift( $dbs, $this->getVar( 'wgDBname' ) ); |
| 166 | } |
| 167 | $conn = false; |
| 168 | $status = new ConnectionStatus; |
| 169 | foreach ( $dbs as $db ) { |
| 170 | try { |
| 171 | $p = [ |
| 172 | 'host' => $this->getVar( 'wgDBserver' ), |
| 173 | 'port' => $this->getVar( 'wgDBport' ), |
| 174 | 'user' => $user, |
| 175 | 'password' => $password ?? '', |
| 176 | 'ssl' => $this->getVar( 'wgDBssl' ), |
| 177 | 'dbname' => $db |
| 178 | ]; |
| 179 | $conn = ( new DatabaseFactory() )->create( 'postgres', $p ); |
| 180 | } catch ( DBConnectionError $error ) { |
| 181 | $conn = false; |
| 182 | $status->fatal( 'config-pg-test-error', $db, |
| 183 | $error->getMessage() ); |
| 184 | } |
| 185 | if ( $conn !== false ) { |
| 186 | break; |
| 187 | } |
| 188 | } |
| 189 | if ( $conn !== false ) { |
| 190 | return new ConnectionStatus( $conn ); |
| 191 | } else { |
| 192 | return $status; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | /** @inheritDoc */ |
| 197 | public function getLocalSettings() { |
| 198 | $port = $this->getVar( 'wgDBport' ); |
| 199 | $useSsl = $this->getVar( 'wgDBssl' ) ? 'true' : 'false'; |
| 200 | $schema = $this->getVar( 'wgDBmwschema' ); |
| 201 | |
| 202 | return "# Postgres specific settings |
| 203 | \$wgDBport = \"{$port}\"; |
| 204 | \$wgDBssl = {$useSsl}; |
| 205 | \$wgDBmwschema = \"{$schema}\";"; |
| 206 | } |
| 207 | |
| 208 | public function preUpgrade() { |
| 209 | global $wgDBuser, $wgDBpassword; |
| 210 | |
| 211 | # Normal user and password are selected after this step, so for now |
| 212 | # just copy these two |
| 213 | $wgDBuser = $this->getVar( '_InstallUser' ); |
| 214 | $wgDBpassword = $this->getVar( '_InstallPassword' ); |
| 215 | } |
| 216 | |
| 217 | /** @inheritDoc */ |
| 218 | public function getGlobalDefaults() { |
| 219 | // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require |
| 220 | // the use of a schema, so we need to set it here |
| 221 | return array_merge( parent::getGlobalDefaults(), [ |
| 222 | 'wgDBmwschema' => 'mediawiki', |
| 223 | ] ); |
| 224 | } |
| 225 | } |