Merge ".mailmap: Correct two contributor names"
[mediawiki.git] / includes / installer / PostgresInstaller.php
blobd8b547397444dfc8ef7d9ff000e24ffd73cf55c2
1 <?php
2 /**
3 * PostgreSQL-specific installer.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
21 * @ingroup Installer
24 namespace MediaWiki\Installer;
26 use InvalidArgumentException;
27 use MediaWiki\MediaWikiServices;
28 use Wikimedia\Rdbms\DatabaseFactory;
29 use Wikimedia\Rdbms\DatabasePostgres;
30 use Wikimedia\Rdbms\DBConnectionError;
31 use Wikimedia\Rdbms\IMaintainableDatabase;
33 /**
34 * Class for setting up the MediaWiki database using Postgres.
36 * @ingroup Installer
37 * @since 1.17
39 class PostgresInstaller extends DatabaseInstaller {
41 /** @inheritDoc */
42 protected $globalNames = [
43 'wgDBserver',
44 'wgDBport',
45 'wgDBname',
46 'wgDBuser',
47 'wgDBpassword',
48 'wgDBssl',
49 'wgDBmwschema',
52 /** @inheritDoc */
53 protected $internalDefaults = [
54 '_InstallUser' => 'postgres',
57 /** @inheritDoc */
58 public static $minimumVersion = '10';
59 /** @inheritDoc */
60 protected static $notMinimumVersionMessage = 'config-postgres-old';
62 public function getName() {
63 return 'postgres';
66 public function isCompiled() {
67 return self::checkExtension( 'pgsql' );
70 public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm {
71 return new PostgresConnectForm( $webInstaller, $this );
74 public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm {
75 return new PostgresSettingsForm( $webInstaller, $this );
78 /**
79 * Open a PG connection with given parameters
80 * @param string $user User name
81 * @param string $password
82 * @param string $dbName Database name
83 * @param string $schema Database schema
84 * @return ConnectionStatus
86 protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
87 $status = new ConnectionStatus;
88 try {
89 $db = MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'postgres', [
90 'host' => $this->getVar( 'wgDBserver' ),
91 'port' => $this->getVar( 'wgDBport' ),
92 'user' => $user,
93 'password' => $password,
94 'ssl' => $this->getVar( 'wgDBssl' ),
95 'dbname' => $dbName,
96 'schema' => $schema,
97 ] );
98 $status->setDB( $db );
99 } catch ( DBConnectionError $e ) {
100 $status->fatal( 'config-connection-error', $e->getMessage() );
103 return $status;
107 * Get a connection of a specific PostgreSQL-specific type. Connections
108 * of a given type are cached.
110 * PostgreSQL lacks cross-database operations, so after the new database is
111 * created, you need to make a separate connection to connect to that
112 * database and add tables to it.
114 * New tables are owned by the user that creates them, and MediaWiki's
115 * PostgreSQL support has always assumed that the table owner will be
116 * $wgDBuser. So before we create new tables, we either need to either
117 * connect as the other user or to execute a SET ROLE command. Using a
118 * separate connection for this allows us to avoid accidental cross-module
119 * dependencies.
121 * @param string $type The type of connection to get:
122 * - self::CONN_CREATE_DATABASE: A connection for creating DBs, suitable for pre-
123 * installation.
124 * - self::CONN_CREATE_SCHEMA: A connection to the new DB, for creating schemas and
125 * other similar objects in the new DB.
126 * - self::CONN_CREATE_TABLES: A connection with a role suitable for creating tables.
127 * @return ConnectionStatus On success, a connection object will be in the value member.
129 protected function openConnection( string $type ) {
130 switch ( $type ) {
131 case self::CONN_CREATE_DATABASE:
132 return $this->openConnectionToAnyDB(
133 $this->getVar( '_InstallUser' ),
134 $this->getVar( '_InstallPassword' ) );
135 case self::CONN_CREATE_SCHEMA:
136 return $this->openConnectionWithParams(
137 $this->getVar( '_InstallUser' ),
138 $this->getVar( '_InstallPassword' ),
139 $this->getVar( 'wgDBname' ),
140 $this->getVar( 'wgDBmwschema' ) );
141 case self::CONN_CREATE_TABLES:
142 $status = $this->openConnection( self::CONN_CREATE_SCHEMA );
143 if ( $status->isOK() ) {
144 $status->merge( $this->changeConnTypeFromSchemaToTables( $status->getDB() ) );
147 return $status;
148 default:
149 throw new InvalidArgumentException( "Invalid connection type: \"$type\"" );
153 protected function changeConnTypeFromSchemaToTables( IMaintainableDatabase $conn ) {
154 if ( !( $conn instanceof DatabasePostgres ) ) {
155 throw new InvalidArgumentException( 'Invalid connection type' );
157 $status = new ConnectionStatus( $conn );
158 $schema = $this->getVar( 'wgDBmwschema' );
159 if ( !$conn->schemaExists( $schema ) ) {
160 $status->fatal( 'config-install-pg-schema-not-exist' );
161 return $status;
163 $conn->determineCoreSchema( $schema );
165 $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
166 $conn->query( "SET ROLE $safeRole", __METHOD__ );
167 return $status;
170 public function openConnectionToAnyDB( $user, $password ) {
171 $dbs = [
172 'template1',
173 'postgres',
175 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
176 array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
178 $conn = false;
179 $status = new ConnectionStatus;
180 foreach ( $dbs as $db ) {
181 try {
182 $p = [
183 'host' => $this->getVar( 'wgDBserver' ),
184 'port' => $this->getVar( 'wgDBport' ),
185 'user' => $user,
186 'password' => $password,
187 'ssl' => $this->getVar( 'wgDBssl' ),
188 'dbname' => $db
190 $conn = ( new DatabaseFactory() )->create( 'postgres', $p );
191 } catch ( DBConnectionError $error ) {
192 $conn = false;
193 $status->fatal( 'config-pg-test-error', $db,
194 $error->getMessage() );
196 if ( $conn !== false ) {
197 break;
200 if ( $conn !== false ) {
201 return new ConnectionStatus( $conn );
202 } else {
203 return $status;
207 public function getLocalSettings() {
208 $port = $this->getVar( 'wgDBport' );
209 $useSsl = $this->getVar( 'wgDBssl' ) ? 'true' : 'false';
210 $schema = $this->getVar( 'wgDBmwschema' );
212 return "# Postgres specific settings
213 \$wgDBport = \"{$port}\";
214 \$wgDBssl = {$useSsl};
215 \$wgDBmwschema = \"{$schema}\";";
218 public function preUpgrade() {
219 global $wgDBuser, $wgDBpassword;
221 # Normal user and password are selected after this step, so for now
222 # just copy these two
223 $wgDBuser = $this->getVar( '_InstallUser' );
224 $wgDBpassword = $this->getVar( '_InstallPassword' );
227 public function getGlobalDefaults() {
228 // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
229 // the use of a schema, so we need to set it here
230 return array_merge( parent::getGlobalDefaults(), [
231 'wgDBmwschema' => 'mediawiki',
232 ] );