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
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
;
34 * Class for setting up the MediaWiki database using Postgres.
39 class PostgresInstaller
extends DatabaseInstaller
{
42 protected $globalNames = [
53 protected $internalDefaults = [
54 '_InstallUser' => 'postgres',
58 public static $minimumVersion = '10';
60 protected static $notMinimumVersionMessage = 'config-postgres-old';
62 public function getName() {
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 );
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
;
89 $db = MediaWikiServices
::getInstance()->getDatabaseFactory()->create( 'postgres', [
90 'host' => $this->getVar( 'wgDBserver' ),
91 'port' => $this->getVar( 'wgDBport' ),
93 'password' => $password,
94 'ssl' => $this->getVar( 'wgDBssl' ),
98 $status->setDB( $db );
99 } catch ( DBConnectionError
$e ) {
100 $status->fatal( 'config-connection-error', $e->getMessage() );
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
121 * @param string $type The type of connection to get:
122 * - self::CONN_CREATE_DATABASE: A connection for creating DBs, suitable for pre-
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 ) {
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() ) );
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' );
163 $conn->determineCoreSchema( $schema );
165 $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
166 $conn->query( "SET ROLE $safeRole", __METHOD__
);
170 public function openConnectionToAnyDB( $user, $password ) {
175 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
176 array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
179 $status = new ConnectionStatus
;
180 foreach ( $dbs as $db ) {
183 'host' => $this->getVar( 'wgDBserver' ),
184 'port' => $this->getVar( 'wgDBport' ),
186 'password' => $password,
187 'ssl' => $this->getVar( 'wgDBssl' ),
190 $conn = ( new DatabaseFactory() )->create( 'postgres', $p );
191 } catch ( DBConnectionError
$error ) {
193 $status->fatal( 'config-pg-test-error', $db,
194 $error->getMessage() );
196 if ( $conn !== false ) {
200 if ( $conn !== false ) {
201 return new ConnectionStatus( $conn );
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',