http://www.mediawiki.org/wiki/Special:Code/MediaWiki/89821

Revision: 89821
Author:   tstarling
Date:     2011-06-10 11:32:57 +0000 (Fri, 10 Jun 2011)
Log Message:
-----------
PostgreSQL install fixes:
* Made PG throw a DBQueryError when it gets a query error, instead of 
DBUnexpectedError. Apparently this mistake goes back to r14625, when exceptions 
were first introduced. Did it by removing reportQueryError(), the DatabaseBase 
version works fine.
* Fixed several places where there was an attempt to check for a query error by 
checking if the result of query() was false. This never worked. Used try/catch 
instead.
* Made the DBConnectionError messages go on one line so that they don't mess up 
the formatting in the installer.
* In DatabasePostgres::selectDB(), only disconnect and reconnect if the DB name 
is actually changing.
* Made DatabasePostgres::schemaExists() less weird and scary.
* Added DatabasePostgres::roleExists() for use by the installer.
* Removed the PostgreSQL-specific hack to make _InstallUser have a default 
other than "root". Made _InstallUser into a proper DBMS-specific internal 
variable instead, since every DBMS we support so far needs a different default.
* Removed the $dbName parameters from openConnection/getConnection, and got rid 
of $this->useAdmin. Implemented a more sophisticated caching scheme instead. 
Partial revert of r89389 and r81440.
* When connecting as the install user before DB creation, and when testing the 
web user's credentials, try a few different database names and use whichever 
one works. 
* Instead of connecting as the web user to create tables, I used SET ROLE. It 
seems cleaner and more like what the other DBMSes do during installation. "SET 
ROLE wikiuser" requires the same privileges as "CREATE SCHEMA ... AUTHORIZATION 
wikiuser", so it's unlikely to break anything.
* In the area of web account creation, fixed various minor logic errors and 
introduced more informative error messages at the submit stage, pre-install. 
Show a helpful error message if the web user exists already and the install 
user can't do the relevant SET ROLE.
* Split schema creation out to a separate install step.
* When creating an account as a non-superuser, add the administrative account 
to the new account's group. This is necessary to avoid a fatal error during 
installation (bug 28845).
* Removed code which alters an existing web user to have appropriate search 
paths and permissions. This may break other apps and is not necessary. As in 
other DBMSes, If the web user exists, it is the responsibility of the sysadmin 
to ensure that it has appropriate permissions.
* Rewrote setupPLpgSQL() to use the query builder functions. 

Modified Paths:
--------------
    trunk/phase3/includes/db/DatabasePostgres.php
    trunk/phase3/includes/installer/DatabaseInstaller.php
    trunk/phase3/includes/installer/Ibm_db2Installer.php
    trunk/phase3/includes/installer/Installer.i18n.php
    trunk/phase3/includes/installer/Installer.php
    trunk/phase3/includes/installer/MysqlInstaller.php
    trunk/phase3/includes/installer/OracleInstaller.php
    trunk/phase3/includes/installer/PostgresInstaller.php
    trunk/phase3/includes/installer/SqliteInstaller.php

Modified: trunk/phase3/includes/db/DatabasePostgres.php
===================================================================
--- trunk/phase3/includes/db/DatabasePostgres.php       2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/db/DatabasePostgres.php       2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -186,7 +186,7 @@
                        wfDebug( "DB connection error\n" );
                        wfDebug( "Server: $server, Database: $dbName, User: 
$user, Password: " . substr( $password, 0, 3 ) . "...\n" );
                        wfDebug( $this->lastError() . "\n" );
-                       throw new DBConnectionError( $this, $phpError );
+                       throw new DBConnectionError( $this, str_replace( "\n", 
' ', $phpError ) );
                }
 
                $this->mOpened = true;
@@ -218,7 +218,11 @@
         * @return
         */
        function selectDB( $db ) {
-               return (bool)$this->open( $this->mServer, $this->mUser, 
$this->mPassword, $db );
+               if ( $this->mDBname !== $db ) {
+                       return (bool)$this->open( $this->mServer, $this->mUser, 
$this->mPassword, $db );
+               } else {
+                       return true;
+               }
        }
 
        function makeConnectionString( $vars ) {
@@ -762,23 +766,6 @@
                return $valuedata;
        }
 
-       function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = 
false ) {
-               // Ignore errors during error handling to avoid infinite 
recursion
-               $ignore = $this->ignoreErrors( true );
-               $this->mErrorCount++;
-
-               if ( $ignore || $tempIgnore ) {
-                       wfDebug( "SQL ERROR (ignored): $error\n" );
-                       $this->ignoreErrors( $ignore );
-               } else {
-                       $message = "A database error has occurred.  Did you 
forget to run maintenance/update.php after upgrading?  See: 
http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n"; .
-                               "Query: $sql\n" .
-                               "Function: $fname\n" .
-                               "Error: $errno $error\n";
-                       throw new DBUnexpectedError( $this, $message );
-               }
-       }
-
        /**
         * @return string wikitext of a link to the server software's web site
         */
@@ -894,22 +881,23 @@
        }
 
        /**
-        * Query whether a given schema exists. Returns the name of the owner
+        * Query whether a given schema exists. Returns true if it does, false 
if it doesn't.
         */
        function schemaExists( $schema ) {
-               $eschema = str_replace( "'", "''", $schema );
-               $SQL = "SELECT rolname FROM pg_catalog.pg_namespace n, 
pg_catalog.pg_roles r "
-                               ."WHERE n.nspowner=r.oid AND n.nspname = 
'$eschema'";
-               $res = $this->query( $SQL );
-               if ( $res && $res->numRows() ) {
-                       $row = $res->fetchObject();
-                       $owner = $row->rolname;
-               } else {
-                       $owner = false;
-               }
-               return $owner;
+               $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
+                       array( 'nspname' => $schema ), __METHOD__ );
+               return (bool)$exists;
        }
 
+       /**
+        * Returns true if a given role (i.e. user) exists, false otherwise.
+        */
+       function roleExists( $roleName ) {
+               $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
+                       array( 'rolname' => $roleName ), __METHOD__ );
+               return (bool)$exists;
+       }
+
        function fieldInfo( $table, $field ) {
                return PostgresField::fromText( $this, $table, $field );
        }

Modified: trunk/phase3/includes/installer/DatabaseInstaller.php
===================================================================
--- trunk/phase3/includes/installer/DatabaseInstaller.php       2011-06-10 
11:32:28 UTC (rev 89820)
+++ trunk/phase3/includes/installer/DatabaseInstaller.php       2011-06-10 
11:32:57 UTC (rev 89821)
@@ -102,7 +102,7 @@
         *
         * @return Status
         */
-       public abstract function openConnection( $dbName = null );
+       public abstract function openConnection();
 
        /**
         * Create the database and return a Status object indicating success or
@@ -121,14 +121,12 @@
         *
         * @return Status
         */
-       public function getConnection( $dbName = null ) {
-               if ( isset($this->db) && $this->db ) { /* Weirdly get E_STRICT
-                                                                               
                * errors without the
-                                                                               
                * isset */
+       public function getConnection() {
+               if ( $this->db ) {
                        return Status::newGood( $this->db );
                }
 
-               $status = $this->openConnection( $dbName );
+               $status = $this->openConnection();
                if ( $status->isOK() ) {
                        $this->db = $status->value;
                        // Enable autocommit

Modified: trunk/phase3/includes/installer/Ibm_db2Installer.php
===================================================================
--- trunk/phase3/includes/installer/Ibm_db2Installer.php        2011-06-10 
11:32:28 UTC (rev 89820)
+++ trunk/phase3/includes/installer/Ibm_db2Installer.php        2011-06-10 
11:32:57 UTC (rev 89821)
@@ -24,6 +24,10 @@
                'wgDBmwschema',
        );
 
+       protected $internalDefaults = array(
+               '_InstallUser' => 'db2admin'
+       );
+
        /**
         * Get the DB2 database extension name
         * @return string
@@ -113,7 +117,7 @@
         * Open a DB2 database connection
         * @return Status
         */
-       public function openConnection( $dbName = null ) {
+       public function openConnection() {
                $status = Status::newGood();
                try {
                        $db = new DatabaseIbm_db2(
@@ -244,4 +248,4 @@
        public function __construct($parent) {
                parent::__construct($parent);
        }
-}
\ No newline at end of file
+}

Modified: trunk/phase3/includes/installer/Installer.i18n.php
===================================================================
--- trunk/phase3/includes/installer/Installer.i18n.php  2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/installer/Installer.i18n.php  2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -211,6 +211,7 @@
        'config-db-schema'                => 'Schema for MediaWiki',
        'config-db-schema-help'           => 'This schema will usually be fine.
 Only change it if you know you need to.',
+       'config-pg-test-error'            => "Cannot connect to database 
'''$1''': $2",
        'config-sqlite-dir'               => 'SQLite data directory:',
        'config-sqlite-dir-help'          => "SQLite stores all data in a 
single file.
 
@@ -486,6 +487,7 @@
        'config-install-step-failed'      => 'failed',
        'config-install-extensions'       => 'Including extensions',
        'config-install-database'         => 'Setting up database',
+       'config-install-schema'           => 'Creating schema',
        'config-install-pg-schema-not-exist' => 'PostgreSQL schema does not 
exist.',
        'config-install-pg-schema-failed' => 'Tables creation failed.
 Make sure that the user "$1" can write to the schema "$2".',
@@ -493,10 +495,17 @@
        'config-install-pg-plpgsql'       => 'Checking for language PL/pgSQL',
        'config-pg-no-plpgsql'            => 'You need to install the language 
PL/pgSQL in the database $1',
        'config-pg-no-create-privs'       => 'The account you specified for 
installation does not have enough privileges to create an account.',
+       'config-pg-not-in-role'           => 'The account you specified for the 
web user already exists.
+The account you specified for installation is not a superuser and is not a 
member of the web user\'s role, so it is unable to create objects owned by the 
web user.
+
+MediaWiki currently requires that the tables be owned by the web user. Please 
specify another web account name, or click "back" and specify a suitably 
privileged install user.',
        'config-install-user'             => 'Creating database user',
        'config-install-user-alreadyexists' => 'User "$1" already exists',
        'config-install-user-create-failed' => 'Creating user "$1" failed: $2',
        'config-install-user-grant-failed'  => 'Granting permission to user 
"$1" failed: $2',
+       'config-install-user-missing'     => 'The specified user "$1" does not 
exist.',
+       'config-install-user-missing-create' => 'The specified user "$1" does 
not exist.
+Please click the "create account" checkbox below if you want to create it.',
        'config-install-tables'           => 'Creating tables',
        'config-install-tables-exist'     => "'''Warning''': MediaWiki tables 
seem to already exist.
 Skipping creation.",

Modified: trunk/phase3/includes/installer/Installer.php
===================================================================
--- trunk/phase3/includes/installer/Installer.php       2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/installer/Installer.php       2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -161,7 +161,6 @@
                '_UpgradeDone' => false,
                '_InstallDone' => false,
                '_Caches' => array(),
-               '_InstallUser' => 'root',
                '_InstallPassword' => '',
                '_SameAccount' => true,
                '_CreateDBAccount' => false,

Modified: trunk/phase3/includes/installer/MysqlInstaller.php
===================================================================
--- trunk/phase3/includes/installer/MysqlInstaller.php  2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/installer/MysqlInstaller.php  2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -27,6 +27,7 @@
        protected $internalDefaults = array(
                '_MysqlEngine' => 'InnoDB',
                '_MysqlCharset' => 'binary',
+               '_InstallUser' => 'root',
        );
 
        public $supportedEngines = array( 'InnoDB', 'MyISAM' );
@@ -126,7 +127,7 @@
        /**
         * @return Status
         */
-       public function openConnection( $dbName = null ) {
+       public function openConnection() {
                $status = Status::newGood();
                try {
                        $db = new DatabaseMysql(

Modified: trunk/phase3/includes/installer/OracleInstaller.php
===================================================================
--- trunk/phase3/includes/installer/OracleInstaller.php 2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/installer/OracleInstaller.php 2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -24,7 +24,8 @@
 
        protected $internalDefaults = array(
                '_OracleDefTS' => 'USERS',
-               '_OracleTempTS' => 'TEMP'
+               '_OracleTempTS' => 'TEMP',
+               '_InstallUser' => 'SYSDBA',
        );
 
        public $minimumVersion = '9.0.1'; // 9iR1
@@ -127,7 +128,7 @@
                return $status;
        }
 
-       public function openConnection( $dbName = null ) {
+       public function openConnection() {
                $status = Status::newGood();
                try {
                        $db = new DatabaseOracle(

Modified: trunk/phase3/includes/installer/PostgresInstaller.php
===================================================================
--- trunk/phase3/includes/installer/PostgresInstaller.php       2011-06-10 
11:32:28 UTC (rev 89820)
+++ trunk/phase3/includes/installer/PostgresInstaller.php       2011-06-10 
11:32:57 UTC (rev 89821)
@@ -23,9 +23,15 @@
                'wgDBmwschema',
        );
 
+       protected $internalDefaults = array(
+               '_InstallUser' => 'postgres',
+       );
+
        var $minimumVersion = '8.3';
-       private $useAdmin = false;
+       var $maxRoleSearchDepth = 5;
 
+       protected $pgConns = array();
+
        function getName() {
                return 'postgres';
        }
@@ -35,11 +41,6 @@
        }
 
        function getConnectForm() {
-               // If this is our first time here, switch the default user 
presented in the form
-               if ( ! $this->getVar('_switchedInstallUser') ) {
-                       $this->setVar('_InstallUser', 'postgres');
-                       $this->setVar('_switchedInstallUser', true);
-               }
                return
                        $this->getTextBox( 'wgDBserver', 'config-db-host', 
array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
                        $this->getTextBox( 'wgDBport', 'config-db-port' ) .
@@ -75,87 +76,185 @@
                        return $status;
                }
 
-               $this->useAdmin = true;
-               // Try to connect
-               $status->merge( $this->getConnection() );
+               $status = $this->getPgConnection( 'create-db' );
                if ( !$status->isOK() ) {
                        return $status;
                }
+               $conn = $status->value;
 
-               //Make sure install user can create
-               if( !$this->canCreateAccounts() ) {
-                       $status->fatal( 'config-pg-no-create-privs' );
-               }
-               if ( !$status->isOK() ) {
-                       return $status;
-               }
-
                // Check version
-               $version = $this->db->getServerVersion();
+               $version = $conn->getServerVersion();
                if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
                        return Status::newFatal( 'config-postgres-old', 
$this->minimumVersion, $version );
                }
 
                $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
                $this->setVar( 'wgDBpassword', $this->getVar( 
'_InstallPassword' ) );
+               return Status::newGood();
+       }
+
+       public function getConnection() {
+               $status = $this->getPgConnection( 'create-tables' );
+               if ( $status->isOK() ) {
+                       $this->db = $status->value;
+               }
                return $status;
        }
 
-       public function openConnection( $dbName = null ) {
+       public function openConnection() {
+               return $this->openPgConnection( 'create-tables' );
+       }
+
+       /**
+        * Open a PG connection with given parameters
+        * @param $user User name
+        * @param $password Password
+        * @param $dbName Database name
+        * @return Status
+        */
+       protected function openConnectionWithParams( $user, $password, $dbName 
) {
                $status = Status::newGood();
                try {
-                       if ( $this->useAdmin ) {
-                               if ( $dbName === null ) $dbName = 'postgres';
+                       $db = new DatabasePostgres(
+                               $this->getVar( 'wgDBserver' ),
+                               $user,
+                               $password,
+                               $dbName);
+                       $status->value = $db;
+               } catch ( DBConnectionError $e ) {
+                       $status->fatal( 'config-connection-error', 
$e->getMessage() );
+               }
+               return $status;
+       }
 
-                               $db = new DatabasePostgres(
-                                       $this->getVar( 'wgDBserver' ),
+       /**
+        * Get a special type of connection
+        * @param $type See openPgConnection() for details.
+        * @return Status
+        */
+       protected function getPgConnection( $type ) {
+               if ( isset( $this->pgConns[$type] ) ) {
+                       return Status::newGood( $this->pgConns[$type] );
+               }
+               $status = $this->openPgConnection( $type );
+
+               if ( $status->isOK() ) {
+                       $conn = $status->value;
+                       $conn->clearFlag( DBO_TRX );
+                       $conn->commit();
+                       $this->pgConns[$type] = $conn;
+               }
+               return $status;
+       }
+
+       /**
+        * Get a connection of a specific PostgreSQL-specific type. Connections
+        * of a given type are cached.
+        *
+        * PostgreSQL lacks cross-database operations, so after the new 
database is 
+        * created, you need to make a separate connection to connect to that 
+        * database and add tables to it. 
+        *
+        * New tables are owned by the user that creates them, and MediaWiki's 
+        * PostgreSQL support has always assumed that the table owner will be 
+        * $wgDBuser. So before we create new tables, we either need to either 
+        * connect as the other user or to execute a SET ROLE command. Using a 
+        * separate connection for this allows us to avoid accidental 
cross-module 
+        * dependencies.
+        *
+        * @param $type The type of connection to get:
+        *    - create-db:     A connection for creating DBs, suitable for pre-
+        *                     installation.
+        *    - create-schema: A connection to the new DB, for creating schemas 
and 
+        *                     other similar objects in the new DB.
+        *    - create-tables: A connection with a role suitable for creating 
tables.
+        *
+        * @return A Status object. On success, a connection object will be in 
the 
+        *   value member.
+        */
+       protected function openPgConnection( $type ) {
+               switch ( $type ) {
+                       case 'create-db':
+                               return $this->openConnectionToAnyDB(
+                                       $this->getVar( '_InstallUser' ), 
+                                       $this->getVar( '_InstallPassword' ) );
+                       case 'create-schema':
+                               return $this->openConnectionWithParams( 
                                        $this->getVar( '_InstallUser' ),
                                        $this->getVar( '_InstallPassword' ),
-                                       $dbName );
-                       } else {
-                               if ( $dbName === null ) $dbName = 
$this->getVar( 'wgDBname' );
+                                       $this->getVar( 'wgDBname' ) );
+                       case 'create-tables':
+                               $status = $this->openPgConnection( 
'create-schema' );
+                               if ( $status->isOK() ) {
+                                       $conn = $status->value;
+                                       $safeRole = $conn->addIdentifierQuotes( 
$this->getVar( 'wgDBuser' ) );
+                                       $conn->query( "SET ROLE $safeRole" );
+                               }
+                               return $status;
+                       default:
+                               throw new MWException( "Invalid special 
connection type: \"$type\"" );
+               }
+       }
 
-                               $db = new DatabasePostgres(
+       public function openConnectionToAnyDB( $user, $password ) {
+               $dbs = array(
+                       'template1',
+                       'postgres',
+               );
+               if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
+                       array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
+               }
+               $status = Status::newGood();
+               foreach ( $dbs as $db ) {
+                       try {
+                               $conn = new DatabasePostgres(
                                        $this->getVar( 'wgDBserver' ),
-                                       $this->getVar( 'wgDBuser' ),
-                                       $this->getVar( 'wgDBpassword' ),
-                                       $dbName );
+                                       $user,
+                                       $password,
+                                       $db );
+                       } catch ( DBConnectionError $error ) {
+                               $conn = false;
+                               $status->fatal( 'config-pg-test-error', $db,
+                                       $error->getMessage() );
                        }
-
-                       if( $db === null ) throw new DBConnectionError("Unknown 
problem while connecting.");
-                       $safeschema = $db->addIdentifierQuotes( $this->getVar( 
'wgDBmwschema' ) );
-                       if( $db->schemaExists( $this->getVar( 'wgDBmwschema' ) 
) ) $db->query( "SET search_path = $safeschema" );
-
-                       $status->value = $db;
-               } catch ( DBConnectionError $e ) {
-                       $status->fatal( 'config-connection-error', 
$e->getMessage() );
+                       if ( $conn !== false ) {
+                               break;
+                       }
                }
-               return $status;
+               if ( $conn !== false ) {
+                       return Status::newGood( $conn );
+               } else {
+                       return $status;
+               }
        }
 
-       protected function canCreateAccounts() {
-               $this->useAdmin = true;
-               $status = $this->getConnection();
+       protected function getInstallUserPermissions() {
+               $status = $this->getPgConnection( 'create-db' );
                if ( !$status->isOK() ) {
                        return false;
                }
                $conn = $status->value;
-
                $superuser = $this->getVar( '_InstallUser' );
 
-               $rights = $conn->selectField( 'pg_catalog.pg_roles',
-                       'CASE WHEN rolsuper then 1
-                                 WHEN rolcreatedb then 2
-                                 ELSE 3
-                        END as rights',
-                       array( 'rolname' => $superuser ), __METHOD__
-               );
+               $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*', 
+                       array( 'rolname' => $superuser ), __METHOD__ );
+               return $row;
+       }
 
-               if( !$rights || $rights == 3 ) {
+       protected function canCreateAccounts() {
+               $perms = $this->getInstallUserPermissions();
+               if ( !$perms ) {
                        return false;
                }
+               return $perms->rolsuper === 't' || $perms->rolcreaterole === 
't';
+       }
 
-               return true;
+       protected function isSuperUser() {
+               $perms = $this->getInstallUserPermissions();
+               if ( !$perms ) {
+                       return false;
+               }
+               return $perms->rolsuper === 't';                
        }
 
        public function getSettingsForm() {
@@ -175,28 +274,112 @@
                        return $status;
                }
 
+               $same = $this->getVar( 'wgDBuser' ) === $this->getVar( 
'_InstallUser' );
+
+               if ( !$same ) {
+                       // Check if the web user exists
+                       // Connect to the database with the install user
+                       $status = $this->getPgConnection( 'create-db' );
+                       if ( !$status->isOK() ) {
+                               return $status;
+                       }
+                       $exists = $status->value->roleExists( $this->getVar( 
'wgDBuser' ) );
+               }
+
                // Validate the create checkbox
-               if ( !$this->canCreateAccounts() ) {
+               if ( $this->canCreateAccounts() && !$same && !$exists ) {
+                       $create = $this->getVar( '_CreateDBAccount' );
+               } else {
                        $this->setVar( '_CreateDBAccount', false );
                        $create = false;
-               } else {
-                       $create = $this->getVar( '_CreateDBAccount' );
                }
 
-               // Don't test the web account if it is the same as the admin.
-               if ( !$create && $this->getVar( 'wgDBuser' ) != $this->getVar( 
'_InstallUser' ) ) {
-                       // Test the web account
-                       try {
-                               $this->useAdmin = false;
-                               return $this->openConnection();
-                       } catch ( DBConnectionError $e ) {
-                               return Status::newFatal( 
'config-connection-error', $e->getMessage() );
+               if ( !$create && !$exists ) {
+                       if ( $this->canCreateAccounts() ) {
+                               $msg = 'config-install-user-missing-create';
+                       } else {
+                               $msg = 'config-install-user-missing';
                        }
+                       return Status::newFatal( $msg, $this->getVar( 
'wgDBuser' ) );
                }
 
-               return Status::newGood();
+               if ( !$exists ) {
+                       // No more checks to do
+                       return Status::newGood();
+               }
+
+               // Existing web account. Test the connection.
+               $status = $this->openConnectionToAnyDB( 
+                       $this->getVar( 'wgDBuser' ),
+                       $this->getVar( 'wgDBpassword' ) );
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // The web user is conventionally the table owner in PostgreSQL 
+               // installations. Make sure the install user is able to create 
+               // objects on behalf of the web user.
+               if ( $this->canCreateObjectsForWebUser() ) {
+                       return Status::newGood();
+               } else {
+                       return Status::newFatal( 'config-pg-not-in-role' );
+               }
        }
 
+       /**
+        * Returns true if the install user is able to create objects owned
+        * by the web user, false otherwise.
+        */
+       protected function canCreateObjectsForWebUser() {
+               if ( $this->isSuperUser() ) {
+                       return true;
+               }
+
+               $status = $this->getPgConnection( 'create-db' );
+               if ( !$status->isOK() ) {
+                       return false;
+               }
+               $conn = $status->value;
+               $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 
'oid',
+                       array( 'rolname' => $this->getVar( '_InstallUser' ) ), 
__METHOD__ );
+               $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
+                       array( 'rolname' => $this->getVar( 'wgDBuser' ) ), 
__METHOD__ );
+
+               return $this->isRoleMember( $conn, $installerId, $webId, 
$this->maxRoleSearchDepth );
+       }
+
+       /**
+        * Recursive helper for canCreateObjectsForWebUser().
+        * @param $conn Database object
+        * @param $targetMember Role ID of the member to look for
+        * @param $group Role ID of the group to look for
+        * @param $maxDepth Maximum recursive search depth
+        */
+       protected function isRoleMember( $conn, $targetMember, $group, 
$maxDepth ) {
+               if ( $targetMember === $group ) {
+                       // A role is always a member of itself
+                       return true;
+               }
+               // Get all members of the given group
+               $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 
'member' ),
+                       array( 'roleid' => $group ), __METHOD__ );
+               foreach ( $res as $row ) {
+                       if ( $row->member == $targetMember ) {
+                               // Found target member
+                               return true;
+                       }
+                       // Recursively search each member of the group to see 
if the target
+                       // is a member of it, up to the given maximum depth.
+                       if ( $maxDepth > 0 ) {
+                               if ( $this->isRoleMember( $conn, $targetMember, 
$row->member, $maxDepth - 1 ) ) {
+                                       // Found member of member
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
        public function preInstall() {
                $commitCB = array(
                        'name' => 'pg-commit',
@@ -206,8 +389,13 @@
                        'name' => 'pg-plpgsql',
                        'callback' => array( $this, 'setupPLpgSQL' ),
                );
+               $schemaCB = array(
+                       'name' => 'schema',
+                       'callback' => array( $this, 'setupSchema' )
+               );
                $this->parent->addInstallStep( $commitCB, 'interwiki' );
                $this->parent->addInstallStep( $plpgCB, 'database' );
+               $this->parent->addInstallStep( $schemaCB, 'database' );
                if( $this->getVar( '_CreateDBAccount' ) ) {
                        $this->parent->addInstallStep( array(
                                'name' => 'user',
@@ -217,12 +405,10 @@
        }
 
        function setupDatabase() {
-               $this->useAdmin = true;
-               $status = $this->getConnection();
+               $status = $this->getPgConnection( 'create-db' );
                if ( !$status->isOK() ) {
                        return $status;
                }
-               $this->setupSchemaVars();
                $conn = $status->value;
 
                $dbName = $this->getVar( 'wgDBname' );
@@ -231,46 +417,45 @@
                $safeschema = $conn->addIdentifierQuotes( $schema );
                $safeuser = $conn->addIdentifierQuotes( $user );
 
-               $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " 
. $conn->addQuotes( $dbName );
-               $rows = $conn->numRows( $conn->query( $SQL ) );
-               $safedb = $conn->addIdentifierQuotes( $dbName );
-               if( !$rows ) {
+               $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
+                       array( 'datname' => $dbName ), __METHOD__ );
+               if ( !$exists ) {
+                       $safedb = $conn->addIdentifierQuotes( $dbName );
                        $conn->query( "CREATE DATABASE $safedb", __METHOD__ );
-                       $conn->query( "GRANT ALL ON DATABASE $safedb to 
$safeuser", __METHOD__ );
-               } else {
-                       $conn->query( "GRANT ALL ON DATABASE $safedb TO 
$safeuser", __METHOD__ );
                }
+               return Status::newGood();
+       }
 
-               // Now that we've established the real database exists, connect 
to it
-               // Because we do not want the same connection, forcibly expire 
the existing conn
-               $this->db = null;
-               $this->useAdmin = false;
-               $status = $this->getConnection();
+       function setupSchema() {
+               // Get a connection to the target database
+               $status = $this->getPgConnection( 'create-schema' );
                if ( !$status->isOK() ) {
                        return $status;
                }
                $conn = $status->value;
 
+               // Create the schema if necessary
+               $schema = $this->getVar( 'wgDBmwschema' );
+               $safeschema = $conn->addIdentifierQuotes( $schema );
+               $safeuser = $conn->addIdentifierQuotes( $this->getVar( 
'wgDBuser' ) );
                if( !$conn->schemaExists( $schema ) ) {
-                       $result = $conn->query( "CREATE SCHEMA $safeschema 
AUTHORIZATION $safeuser" );
-                       if( !$result ) {
-                               $status->fatal( 
'config-install-pg-schema-failed', $user, $schema );
+                       try {
+                               $conn->query( "CREATE SCHEMA $safeschema 
AUTHORIZATION $safeuser" );
+                       } catch ( DBQueryError $e ) {
+                               return Status::newFatal( 
'config-install-pg-schema-failed', 
+                                       $this->getVar( '_InstallUser' ), 
$schema );
                        }
-               } else {
-                       $safeschema2 = $conn->addQuotes( $schema );
-                       $SQL = "SELECT 'GRANT ALL ON 
'||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
-                               "FROM pg_catalog.pg_class p, 
pg_catalog.pg_namespace n\n" .
-                               "WHERE relnamespace = n.oid AND n.nspname = 
$safeschema2\n" .
-                               "AND p.relkind IN ('r','S','v')\n";
-                       $SQL .= "UNION\n";
-                       $SQL .= "SELECT 'GRANT ALL ON FUNCTION 
'||pg_catalog.quote_ident(proname)||'('||\n".
-                               "pg_catalog.oidvectortypes(p.proargtypes)||') 
TO $safeuser;'\n" .
-                               "FROM pg_catalog.pg_proc p, 
pg_catalog.pg_namespace n\n" .
-                               "WHERE p.pronamespace = n.oid AND n.nspname = 
$safeschema2";
-                       $conn->query( "SET search_path = $safeschema" );
-                       $res = $conn->query( $SQL );
                }
-               return $status;
+
+               // If we created a user, alter it now to search the new schema 
by default
+               if ( $this->getVar( '_CreateDBAccount' ) ) {
+                       $conn->query( "ALTER ROLE $safeuser SET search_path = 
$safeschema, public", 
+                               __METHOD__ );
+               }
+
+               // Select the new schema in the current connection
+               $conn->query( "SET search_path = $safeschema" );
+               return Status::newGood();
        }
 
        function commitChanges() {
@@ -283,34 +468,39 @@
                        return Status::newGood();
                }
 
-               $this->useAdmin = true;
-               $status = $this->getConnection();
-
+               $status = $this->getPgConnection( 'create-db' );
                if ( !$status->isOK() ) {
                        return $status;
                }
+               $conn = $status->value;
 
                $schema = $this->getVar( 'wgDBmwschema' );
-               $safeuser = $this->db->addIdentifierQuotes( $this->getVar( 
'wgDBuser' ) );
-               $safeusercheck = $this->db->addQuotes( $this->getVar( 
'wgDBuser' ) );
-               $safepass = $this->db->addQuotes( $this->getVar( 'wgDBpassword' 
) );
-               $safeschema = $this->db->addIdentifierQuotes( $schema );
+               $safeuser = $conn->addIdentifierQuotes( $this->getVar( 
'wgDBuser' ) );
+               $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
+               $safeschema = $conn->addIdentifierQuotes( $schema );
 
-               $rows = $this->db->numRows(
-                       $this->db->query( "SELECT 1 FROM pg_catalog.pg_roles 
WHERE rolname = $safeusercheck" )
-               );
-               if ( $rows < 1 ) {
-                       $res = $this->db->query( "CREATE ROLE $safeuser 
NOCREATEDB LOGIN PASSWORD $safepass", __METHOD__ );
-                       if ( $res !== true && !( $res instanceOf ResultWrapper 
) ) {
-                               $status->fatal( 'config-install-user-failed', 
$this->getVar( 'wgDBuser' ), $res );
+               // Check if the user already exists
+               $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
+               if ( !$userExists ) {
+                       // Create the user
+                       try {
+                               $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN 
PASSWORD $safepass";
+                               
+                               // If the install user is not a superuser, we 
need to make the install 
+                               // user a member of the new user's group, so 
that the install user will
+                               // be able to create a schema and other objects 
on behalf of the new user.
+                               if ( !$this->isSuperUser() ) {
+                                       $sql .= ' ROLE' . 
$conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) );
+                               }
+
+                               $conn->query( $sql, __METHOD__ );
+                       } catch ( DBQueryError $e ) {
+                               return Status::newFatal( 
'config-install-user-create-failed', 
+                                       $this->getVar( 'wgDBuser' ), 
$e->getMessage() );
                        }
-                       if( $status->isOK() ) {
-                               $this->db->query("ALTER ROLE $safeuser LOGIN");
-                       }
                }
-               $this->db->query("ALTER ROLE $safeuser SET search_path = 
$safeschema, public");
 
-               return $status;
+               return Status::newGood();
        }
 
        function getLocalSettings() {
@@ -334,32 +524,30 @@
        public function createTables() {
                $schema = $this->getVar( 'wgDBmwschema' );
 
-               $this->db = null;
-               $this->useAdmin = false;
                $status = $this->getConnection();
                if ( !$status->isOK() ) {
                        return $status;
                }
+               $conn = $status->value;
 
-               if( $this->db->tableExists( 'user' ) ) {
+               if( $conn->tableExists( 'user' ) ) {
                        $status->warning( 'config-install-tables-exist' );
                        return $status;
                }
 
-               $this->db->begin( __METHOD__ );
+               $conn->begin( __METHOD__ );
 
-               // getConnection() should have already selected the schema if 
it exists
-               if( !$this->db->schemaExists( $schema ) ) {
-                       $status->error( 'config-install-pg-schema-not-exist' );
+               if( !$conn->schemaExists( $schema ) ) {
+                       $status->fatal( 'config-install-pg-schema-not-exist' );
                        return $status;
                }
-               $error = $this->db->sourceFile( $this->db->getSchema() );
+               $error = $conn->sourceFile( $conn->getSchema() );
                if( $error !== true ) {
-                       $this->db->reportQueryError( $error, 0, '', __METHOD__ 
);
-                       $this->db->rollback( __METHOD__ );
+                       $conn->reportQueryError( $error, 0, '', __METHOD__ );
+                       $conn->rollback( __METHOD__ );
                        $status->fatal( 'config-install-tables-failed', $error 
);
                } else {
-                       $this->db->commit( __METHOD__ );
+                       $conn->commit( __METHOD__ );
                }
                // Resume normal operations
                if( $status->isOk() ) {
@@ -369,34 +557,40 @@
        }
 
        public function setupPLpgSQL() {
-               $this->db = null;
-               $this->useAdmin = true;
-               $dbName = $this->getVar( 'wgDBname' );
-               $status = $this->getConnection( $dbName );
+               // Connect as the install user, since it owns the database and 
so is 
+               // the user that needs to run "CREATE LANGAUGE"
+               $status = $this->getPgConnection( 'create-schema' );
                if ( !$status->isOK() ) {
                        return $status;
                }
-               $this->db = $status->value;
+               $conn = $status->value;
 
-               /* Admin user has to be connected to the db it just
-                  created to satisfy ownership requirements for
-                  "CREATE LANGAUGE" */
-               $rows = $this->db->numRows(
-                       $this->db->query( "SELECT 1 FROM pg_catalog.pg_language 
WHERE lanname = 'plpgsql'" )
-               );
-               if ( $rows < 1 ) {
-                       // plpgsql is not installed, but if we have a 
pg_pltemplate table, we should be able to create it
-                       $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN 
pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
-                               "WHERE relname = 'pg_pltemplate' AND 
nspname='pg_catalog'";
-                       $rows = $this->db->numRows( $this->db->query( $SQL ) );
-                       if ( $rows >= 1 ) {
-                               $result = $this->db->query( 'CREATE LANGUAGE 
plpgsql' );
-                               if ( !$result ) {
-                                       return Status::newFatal( 
'config-pg-no-plpgsql', $dbName );
-                               }
-                       } else {
-                               return Status::newFatal( 
'config-pg-no-plpgsql', $dbName );
+               $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
+                       array( 'lanname' => 'plpgsql' ), __METHOD__ );
+               if ( $exists ) {
+                       // Already exists, nothing to do
+                       return Status::newGood();
+               }
+
+               // plpgsql is not installed, but if we have a pg_pltemplate 
table, we 
+               // should be able to create it
+               $exists = $conn->selectField(
+                       array( '"pg_catalog"."pg_class"', 
'"pg_catalog"."pg_namespace"' ),
+                       1,
+                       array(
+                               'pg_namespace.oid=relnamespace',
+                               'nspname' => 'pg_catalog',
+                               'relname' => 'pg_pltemplate',
+                       ),
+                       __METHOD__ );
+               if ( $exists ) {
+                       try {
+                               $conn->query( 'CREATE LANGUAGE plpgsql' );
+                       } catch ( DBQueryError $e ) {
+                               return Status::newFatal( 
'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
                        }
+               } else {
+                       return Status::newFatal( 'config-pg-no-plpgsql', 
$this->getVar( 'wgDBname' ) );
                }
                return Status::newGood();
        }

Modified: trunk/phase3/includes/installer/SqliteInstaller.php
===================================================================
--- trunk/phase3/includes/installer/SqliteInstaller.php 2011-06-10 11:32:28 UTC 
(rev 89820)
+++ trunk/phase3/includes/installer/SqliteInstaller.php 2011-06-10 11:32:57 UTC 
(rev 89821)
@@ -122,7 +122,7 @@
        /**
         * @return Status
         */
-       public function openConnection( $dbName = null ) {
+       public function openConnection() {
                global $wgSQLiteDataDir;
 
                $status = Status::newGood();


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to