3 * PostgreSQL-specific installer.
10 * Class for setting up the MediaWiki database using Postgres.
15 class PostgresInstaller
extends DatabaseInstaller
{
17 protected $globalNames = array(
26 var $minimumVersion = '8.3';
27 private $useAdmin = false;
33 public function isCompiled() {
34 return self
::checkExtension( 'pgsql' );
37 function getConnectForm() {
38 // If this is our first time here, switch the default user presented in the form
39 if ( ! $this->getVar('_switchedInstallUser') ) {
40 $this->setVar('_InstallUser', 'postgres');
41 $this->setVar('_switchedInstallUser', true);
44 $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent
->getHelpBox( 'config-db-host-help' ) ) .
45 $this->getTextBox( 'wgDBport', 'config-db-port' ) .
46 Html
::openElement( 'fieldset' ) .
47 Html
::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
48 $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent
->getHelpBox( 'config-db-name-help' ) ) .
49 $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array(), $this->parent
->getHelpBox( 'config-db-schema-help' ) ) .
50 Html
::closeElement( 'fieldset' ) .
51 $this->getInstallUserBox();
54 function submitConnectForm() {
55 // Get variables from the request
56 $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport',
57 'wgDBname', 'wgDBmwschema' ) );
60 $status = Status
::newGood();
61 if ( !strlen( $newValues['wgDBname'] ) ) {
62 $status->fatal( 'config-missing-db-name' );
63 } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
64 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
66 if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
67 $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
71 if ( $status->isOK() ) {
72 $status->merge( $this->submitInstallUserBox() );
74 if ( !$status->isOK() ) {
78 $this->useAdmin
= true;
80 $status->merge( $this->getConnection() );
81 if ( !$status->isOK() ) {
85 //Make sure install user can create
86 if( !$this->canCreateAccounts() ) {
87 $status->fatal( 'config-pg-no-create-privs' );
89 if ( !$status->isOK() ) {
94 $version = $this->db
->getServerVersion();
95 if ( version_compare( $version, $this->minimumVersion
) < 0 ) {
96 return Status
::newFatal( 'config-postgres-old', $this->minimumVersion
, $version );
99 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
100 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
104 public function openConnection() {
105 $status = Status
::newGood();
107 if ( $this->useAdmin
) {
108 $db = new DatabasePostgres(
109 $this->getVar( 'wgDBserver' ),
110 $this->getVar( '_InstallUser' ),
111 $this->getVar( '_InstallPassword' ),
114 $db = new DatabasePostgres(
115 $this->getVar( 'wgDBserver' ),
116 $this->getVar( 'wgDBuser' ),
117 $this->getVar( 'wgDBpassword' ),
118 $this->getVar( 'wgDBname' ) );
120 $status->value
= $db;
121 } catch ( DBConnectionError
$e ) {
122 $status->fatal( 'config-connection-error', $e->getMessage() );
127 protected function canCreateAccounts() {
128 $this->useAdmin
= true;
129 $status = $this->getConnection();
130 if ( !$status->isOK() ) {
133 $conn = $status->value
;
135 $superuser = $this->getVar( '_InstallUser' );
137 $rights = $conn->selectField( 'pg_catalog.pg_user',
138 'CASE WHEN usesuper IS TRUE THEN
139 CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
140 ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
142 array( 'usename' => $superuser ), __METHOD__
145 if( !$rights ||
( $rights != 1 && $rights != 3 ) ) {
152 public function getSettingsForm() {
153 if ( $this->canCreateAccounts() ) {
154 $noCreateMsg = false;
156 $noCreateMsg = 'config-db-web-no-create-privs';
158 $s = $this->getWebUserBox( $noCreateMsg );
163 public function submitSettingsForm() {
164 $status = $this->submitWebUserBox();
165 if ( !$status->isOK() ) {
169 // Validate the create checkbox
170 if ( !$this->canCreateAccounts() ) {
171 $this->setVar( '_CreateDBAccount', false );
174 $create = $this->getVar( '_CreateDBAccount' );
177 // Don't test the web account if it is the same as the admin.
178 if ( !$create && $this->getVar( 'wgDBuser' ) != $this->getVar( '_InstallUser' ) ) {
179 // Test the web account
181 $this->useAdmin
= false;
182 return $this->openConnection();
183 } catch ( DBConnectionError
$e ) {
184 return Status
::newFatal( 'config-connection-error', $e->getMessage() );
188 return Status
::newGood();
191 public function preInstall() {
193 'name' => 'pg-commit',
194 'callback' => array( $this, 'commitChanges' ),
197 'name' => 'pg-plpgsql',
198 'callback' => array( $this, 'setupPLpgSQL' ),
200 $this->parent
->addInstallStep( $commitCB, 'interwiki' );
201 $this->parent
->addInstallStep( $plpgCB, 'database' );
202 if( $this->getVar( '_CreateDBAccount' ) ) {
203 $this->parent
->addInstallStep( array(
205 'callback' => array( $this, 'setupUser' ),
210 function setupDatabase() {
211 $this->useAdmin
= true;
212 $status = $this->getConnection();
213 if ( !$status->isOK() ) {
216 $this->setupSchemaVars();
217 $conn = $status->value
;
219 $dbName = $this->getVar( 'wgDBname' );
220 $schema = $this->getVar( 'wgDBmwschema' );
221 $user = $this->getVar( 'wgDBuser' );
222 $safeschema = $conn->addIdentifierQuotes( $schema );
223 $safeuser = $conn->addIdentifierQuotes( $user );
225 $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $conn->addQuotes( $dbName );
226 $rows = $conn->numRows( $conn->query( $SQL ) );
227 $safedb = $conn->addIdentifierQuotes( $dbName );
229 $conn->query( "CREATE DATABASE $safedb OWNER $safeuser", __METHOD__
);
231 $conn->query( "ALTER DATABASE $safedb OWNER TO $safeuser", __METHOD__
);
234 // Now that we've established the real database exists, connect to it
235 // Because we do not want the same connection, forcibly expire the existing conn
237 $this->useAdmin
= false;
238 $status = $this->getConnection();
239 if ( !$status->isOK() ) {
242 $conn = $status->value
;
244 if( !$conn->schemaExists( $schema ) ) {
245 $result = $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
247 $status->fatal( 'config-install-pg-schema-failed', $user, $schema );
250 $safeschema2 = $conn->addQuotes( $schema );
251 $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
252 "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n" .
253 "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n" .
254 "AND p.relkind IN ('r','S','v')\n";
256 $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n".
257 "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n" .
258 "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n" .
259 "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2";
260 $conn->query( "SET search_path = $safeschema" );
261 $res = $conn->query( $SQL );
266 function commitChanges() {
267 $this->db
->query( 'COMMIT' );
268 return Status
::newGood();
271 function setupUser() {
272 if ( !$this->getVar( '_CreateDBAccount' ) ) {
273 return Status
::newGood();
276 $this->useAdmin
= true;
277 $status = $this->getConnection();
279 if ( !$status->isOK() ) {
283 $schema = $this->getVar( 'wgDBmwschema' );
284 $safeuser = $this->db
->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
285 $safeusercheck = $this->db
->addQuotes( $this->getVar( 'wgDBuser' ) );
286 $safepass = $this->db
->addQuotes( $this->getVar( 'wgDBpassword' ) );
287 $safeschema = $this->db
->addIdentifierQuotes( $schema );
289 $rows = $this->db
->numRows(
290 $this->db
->query( "SELECT 1 FROM pg_catalog.pg_shadow WHERE usename = $safeusercheck" )
293 $res = $this->db
->query( "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass", __METHOD__
);
294 if ( $res !== true && !( $res instanceOf ResultWrapper
) ) {
295 $status->fatal( 'config-install-user-failed', $this->getVar( 'wgDBuser' ), $res );
297 if( $status->isOK() ) {
298 $this->db
->query("ALTER USER $safeuser SET search_path = $safeschema");
305 function getLocalSettings() {
306 $port = $this->getVar( 'wgDBport' );
307 $schema = $this->getVar( 'wgDBmwschema' );
309 "# Postgres specific settings
310 \$wgDBport = \"{$port}\";
311 \$wgDBmwschema = \"{$schema}\";";
314 public function preUpgrade() {
315 global $wgDBuser, $wgDBpassword;
317 # Normal user and password are selected after this step, so for now
318 # just copy these two
319 $wgDBuser = $this->getVar( '_InstallUser' );
320 $wgDBpassword = $this->getVar( '_InstallPassword' );
323 public function createTables() {
324 $schema = $this->getVar( 'wgDBmwschema' );
327 $this->useAdmin
= false;
328 $status = $this->getConnection();
329 if ( !$status->isOK() ) {
333 if( $this->db
->tableExists( 'user' ) ) {
334 $status->warning( 'config-install-tables-exist' );
338 $this->db
->begin( __METHOD__
);
340 if( !$this->db
->schemaExists( $schema ) ) {
341 $status->error( 'config-install-pg-schema-not-exist' );
344 $safeschema = $this->db
->addIdentifierQuotes( $schema );
345 $this->db
->query( "SET search_path = $safeschema" );
346 $error = $this->db
->sourceFile( $this->db
->getSchema() );
347 if( $error !== true ) {
348 $this->db
->reportQueryError( $error, 0, '', __METHOD__
);
349 $this->db
->rollback( __METHOD__
);
350 $status->fatal( 'config-install-tables-failed', $error );
352 $this->db
->commit( __METHOD__
);
354 // Resume normal operations
355 if( $status->isOk() ) {
361 public function setupPLpgSQL() {
362 $this->useAdmin
= true;
363 $status = $this->getConnection();
364 if ( !$status->isOK() ) {
368 $rows = $this->db
->numRows(
369 $this->db
->query( "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'" )
372 // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
373 $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
374 "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'";
375 $rows = $this->db
->numRows( $this->db
->query( $SQL ) );
376 $dbName = $this->getVar( 'wgDBname' );
378 $result = $this->db
->query( 'CREATE LANGUAGE plpgsql' );
380 return Status
::newFatal( 'config-pg-no-plpgsql', $dbName );
383 return Status
::newFatal( 'config-pg-no-plpgsql', $dbName );
386 return Status
::newGood();