MediaWiki  REL1_19
PostgresInstaller.php
Go to the documentation of this file.
00001 <?php
00015 class PostgresInstaller extends DatabaseInstaller {
00016 
00017         protected $globalNames = array(
00018                 'wgDBserver',
00019                 'wgDBport',
00020                 'wgDBname',
00021                 'wgDBuser',
00022                 'wgDBpassword',
00023                 'wgDBmwschema',
00024         );
00025 
00026         protected $internalDefaults = array(
00027                 '_InstallUser' => 'postgres',
00028         );
00029 
00030         var $minimumVersion = '8.3';
00031         var $maxRoleSearchDepth = 5;
00032 
00033         protected $pgConns = array();
00034 
00035         function getName() {
00036                 return 'postgres';
00037         }
00038 
00039         public function isCompiled() {
00040                 return self::checkExtension( 'pgsql' );
00041         }
00042 
00043         function getConnectForm() {
00044                 return
00045                         $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
00046                         $this->getTextBox( 'wgDBport', 'config-db-port' ) .
00047                         Html::openElement( 'fieldset' ) .
00048                         Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
00049                         $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
00050                         $this->getTextBox( 'wgDBmwschema', 'config-db-schema', array(), $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
00051                         Html::closeElement( 'fieldset' ) .
00052                         $this->getInstallUserBox();
00053         }
00054 
00055         function submitConnectForm() {
00056                 // Get variables from the request
00057                 $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport',
00058                         'wgDBname', 'wgDBmwschema' ) );
00059 
00060                 // Validate them
00061                 $status = Status::newGood();
00062                 if ( !strlen( $newValues['wgDBname'] ) ) {
00063                         $status->fatal( 'config-missing-db-name' );
00064                 } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
00065                         $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
00066                 }
00067                 if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
00068                         $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
00069                 }
00070 
00071                 // Submit user box
00072                 if ( $status->isOK() ) {
00073                         $status->merge( $this->submitInstallUserBox() );
00074                 }
00075                 if ( !$status->isOK() ) {
00076                         return $status;
00077                 }
00078 
00079                 $status = $this->getPgConnection( 'create-db' );
00080                 if ( !$status->isOK() ) {
00081                         return $status;
00082                 }
00086                 $conn = $status->value;
00087 
00088                 // Check version
00089                 $version = $conn->getServerVersion();
00090                 if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
00091                         return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version );
00092                 }
00093 
00094                 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
00095                 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
00096                 return Status::newGood();
00097         }
00098 
00099         public function getConnection() {
00100                 $status = $this->getPgConnection( 'create-tables' );
00101                 if ( $status->isOK() ) {
00102                         $this->db = $status->value;
00103                 }
00104                 return $status;
00105         }
00106 
00107         public function openConnection() {
00108                 return $this->openPgConnection( 'create-tables' );
00109         }
00110 
00118         protected function openConnectionWithParams( $user, $password, $dbName ) {
00119                 $status = Status::newGood();
00120                 try {
00121                         $db = new DatabasePostgres(
00122                                 $this->getVar( 'wgDBserver' ),
00123                                 $user,
00124                                 $password,
00125                                 $dbName);
00126                         $status->value = $db;
00127                 } catch ( DBConnectionError $e ) {
00128                         $status->fatal( 'config-connection-error', $e->getMessage() );
00129                 }
00130                 return $status;
00131         }
00132 
00138         protected function getPgConnection( $type ) {
00139                 if ( isset( $this->pgConns[$type] ) ) {
00140                         return Status::newGood( $this->pgConns[$type] );
00141                 }
00142                 $status = $this->openPgConnection( $type );
00143 
00144                 if ( $status->isOK() ) {
00148                         $conn = $status->value;
00149                         $conn->clearFlag( DBO_TRX );
00150                         $conn->commit();
00151                         $this->pgConns[$type] = $conn;
00152                 }
00153                 return $status;
00154         }
00155 
00181         protected function openPgConnection( $type ) {
00182                 switch ( $type ) {
00183                         case 'create-db':
00184                                 return $this->openConnectionToAnyDB(
00185                                         $this->getVar( '_InstallUser' ),
00186                                         $this->getVar( '_InstallPassword' ) );
00187                         case 'create-schema':
00188                                 return $this->openConnectionWithParams(
00189                                         $this->getVar( '_InstallUser' ),
00190                                         $this->getVar( '_InstallPassword' ),
00191                                         $this->getVar( 'wgDBname' ) );
00192                         case 'create-tables':
00193                                 $status = $this->openPgConnection( 'create-schema' );
00194                                 if ( $status->isOK() ) {
00198                                         $conn = $status->value;
00199                                         $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
00200                                         $conn->query( "SET ROLE $safeRole" );
00201                                 }
00202                                 return $status;
00203                         default:
00204                                 throw new MWException( "Invalid special connection type: \"$type\"" );
00205                 }
00206         }
00207 
00208         public function openConnectionToAnyDB( $user, $password ) {
00209                 $dbs = array(
00210                         'template1',
00211                         'postgres',
00212                 );
00213                 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
00214                         array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
00215                 }
00216                 $conn = false;
00217                 $status = Status::newGood();
00218                 foreach ( $dbs as $db ) {
00219                         try {
00220                                 $conn = new DatabasePostgres(
00221                                         $this->getVar( 'wgDBserver' ),
00222                                         $user,
00223                                         $password,
00224                                         $db );
00225                         } catch ( DBConnectionError $error ) {
00226                                 $conn = false;
00227                                 $status->fatal( 'config-pg-test-error', $db,
00228                                         $error->getMessage() );
00229                         }
00230                         if ( $conn !== false ) {
00231                                 break;
00232                         }
00233                 }
00234                 if ( $conn !== false ) {
00235                         return Status::newGood( $conn );
00236                 } else {
00237                         return $status;
00238                 }
00239         }
00240 
00241         protected function getInstallUserPermissions() {
00242                 $status = $this->getPgConnection( 'create-db' );
00243                 if ( !$status->isOK() ) {
00244                         return false;
00245                 }
00249                 $conn = $status->value;
00250                 $superuser = $this->getVar( '_InstallUser' );
00251 
00252                 $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
00253                         array( 'rolname' => $superuser ), __METHOD__ );
00254                 return $row;
00255         }
00256 
00257         protected function canCreateAccounts() {
00258                 $perms = $this->getInstallUserPermissions();
00259                 if ( !$perms ) {
00260                         return false;
00261                 }
00262                 return $perms->rolsuper === 't' || $perms->rolcreaterole === 't';
00263         }
00264 
00265         protected function isSuperUser() {
00266                 $perms = $this->getInstallUserPermissions();
00267                 if ( !$perms ) {
00268                         return false;
00269                 }
00270                 return $perms->rolsuper === 't';
00271         }
00272 
00273         public function getSettingsForm() {
00274                 if ( $this->canCreateAccounts() ) {
00275                         $noCreateMsg = false;
00276                 } else {
00277                         $noCreateMsg = 'config-db-web-no-create-privs';
00278                 }
00279                 $s = $this->getWebUserBox( $noCreateMsg );
00280 
00281                 return $s;
00282         }
00283 
00284         public function submitSettingsForm() {
00285                 $status = $this->submitWebUserBox();
00286                 if ( !$status->isOK() ) {
00287                         return $status;
00288                 }
00289 
00290                 $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
00291 
00292                 if ( $same ) {
00293                         $exists = true;
00294                 } else {
00295                         // Check if the web user exists
00296                         // Connect to the database with the install user
00297                         $status = $this->getPgConnection( 'create-db' );
00298                         if ( !$status->isOK() ) {
00299                                 return $status;
00300                         }
00301                         $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
00302                 }
00303 
00304                 // Validate the create checkbox
00305                 if ( $this->canCreateAccounts() && !$same && !$exists ) {
00306                         $create = $this->getVar( '_CreateDBAccount' );
00307                 } else {
00308                         $this->setVar( '_CreateDBAccount', false );
00309                         $create = false;
00310                 }
00311 
00312                 if ( !$create && !$exists ) {
00313                         if ( $this->canCreateAccounts() ) {
00314                                 $msg = 'config-install-user-missing-create';
00315                         } else {
00316                                 $msg = 'config-install-user-missing';
00317                         }
00318                         return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
00319                 }
00320 
00321                 if ( !$exists ) {
00322                         // No more checks to do
00323                         return Status::newGood();
00324                 }
00325 
00326                 // Existing web account. Test the connection.
00327                 $status = $this->openConnectionToAnyDB(
00328                         $this->getVar( 'wgDBuser' ),
00329                         $this->getVar( 'wgDBpassword' ) );
00330                 if ( !$status->isOK() ) {
00331                         return $status;
00332                 }
00333 
00334                 // The web user is conventionally the table owner in PostgreSQL
00335                 // installations. Make sure the install user is able to create
00336                 // objects on behalf of the web user.
00337                 if ( $same || $this->canCreateObjectsForWebUser() ) {
00338                         return Status::newGood();
00339                 } else {
00340                         return Status::newFatal( 'config-pg-not-in-role' );
00341                 }
00342         }
00343 
00348         protected function canCreateObjectsForWebUser() {
00349                 if ( $this->isSuperUser() ) {
00350                         return true;
00351                 }
00352 
00353                 $status = $this->getPgConnection( 'create-db' );
00354                 if ( !$status->isOK() ) {
00355                         return false;
00356                 }
00357                 $conn = $status->value;
00358                 $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
00359                         array( 'rolname' => $this->getVar( '_InstallUser' ) ), __METHOD__ );
00360                 $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
00361                         array( 'rolname' => $this->getVar( 'wgDBuser' ) ), __METHOD__ );
00362 
00363                 return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth );
00364         }
00365 
00373         protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
00374                 if ( $targetMember === $group ) {
00375                         // A role is always a member of itself
00376                         return true;
00377                 }
00378                 // Get all members of the given group
00379                 $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 'member' ),
00380                         array( 'roleid' => $group ), __METHOD__ );
00381                 foreach ( $res as $row ) {
00382                         if ( $row->member == $targetMember ) {
00383                                 // Found target member
00384                                 return true;
00385                         }
00386                         // Recursively search each member of the group to see if the target
00387                         // is a member of it, up to the given maximum depth.
00388                         if ( $maxDepth > 0 ) {
00389                                 if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) {
00390                                         // Found member of member
00391                                         return true;
00392                                 }
00393                         }
00394                 }
00395                 return false;
00396         }
00397 
00398         public function preInstall() {
00399                 $createDbAccount = array(
00400                         'name' => 'user',
00401                         'callback' => array( $this, 'setupUser' ),
00402                 );
00403                 $commitCB = array(
00404                         'name' => 'pg-commit',
00405                         'callback' => array( $this, 'commitChanges' ),
00406                 );
00407                 $plpgCB = array(
00408                         'name' => 'pg-plpgsql',
00409                         'callback' => array( $this, 'setupPLpgSQL' ),
00410                 );
00411                 $schemaCB = array(
00412                         'name' => 'schema',
00413                         'callback' => array( $this, 'setupSchema' )
00414                 );
00415 
00416                 if( $this->getVar( '_CreateDBAccount' ) ) {
00417                         $this->parent->addInstallStep( $createDbAccount, 'database' );
00418                 }
00419                 $this->parent->addInstallStep( $commitCB, 'interwiki' );
00420                 $this->parent->addInstallStep( $plpgCB, 'database' );
00421                 $this->parent->addInstallStep( $schemaCB, 'database' );
00422         }
00423 
00424         function setupDatabase() {
00425                 $status = $this->getPgConnection( 'create-db' );
00426                 if ( !$status->isOK() ) {
00427                         return $status;
00428                 }
00429                 $conn = $status->value;
00430 
00431                 $dbName = $this->getVar( 'wgDBname' );
00432                 //$schema = $this->getVar( 'wgDBmwschema' );
00433                 //$user = $this->getVar( 'wgDBuser' );
00434                 //$safeschema = $conn->addIdentifierQuotes( $schema );
00435                 //$safeuser = $conn->addIdentifierQuotes( $user );
00436 
00437                 $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
00438                         array( 'datname' => $dbName ), __METHOD__ );
00439                 if ( !$exists ) {
00440                         $safedb = $conn->addIdentifierQuotes( $dbName );
00441                         $conn->query( "CREATE DATABASE $safedb", __METHOD__ );
00442                 }
00443                 return Status::newGood();
00444         }
00445 
00446         function setupSchema() {
00447                 // Get a connection to the target database
00448                 $status = $this->getPgConnection( 'create-schema' );
00449                 if ( !$status->isOK() ) {
00450                         return $status;
00451                 }
00452                 $conn = $status->value;
00453 
00454                 // Create the schema if necessary
00455                 $schema = $this->getVar( 'wgDBmwschema' );
00456                 $safeschema = $conn->addIdentifierQuotes( $schema );
00457                 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
00458                 if( !$conn->schemaExists( $schema ) ) {
00459                         try {
00460                                 $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
00461                         } catch ( DBQueryError $e ) {
00462                                 return Status::newFatal( 'config-install-pg-schema-failed',
00463                                         $this->getVar( '_InstallUser' ), $schema );
00464                         }
00465                 }
00466 
00467                 // If we created a user, alter it now to search the new schema by default
00468                 if ( $this->getVar( '_CreateDBAccount' ) ) {
00469                         $conn->query( "ALTER ROLE $safeuser SET search_path = $safeschema, public",
00470                                 __METHOD__ );
00471                 }
00472 
00473                 // Select the new schema in the current connection
00474                 $conn->query( "SET search_path = $safeschema" );
00475                 return Status::newGood();
00476         }
00477 
00478         function commitChanges() {
00479                 $this->db->commit();
00480                 return Status::newGood();
00481         }
00482 
00483         function setupUser() {
00484                 if ( !$this->getVar( '_CreateDBAccount' ) ) {
00485                         return Status::newGood();
00486                 }
00487 
00488                 $status = $this->getPgConnection( 'create-db' );
00489                 if ( !$status->isOK() ) {
00490                         return $status;
00491                 }
00492                 $conn = $status->value;
00493 
00494                 //$schema = $this->getVar( 'wgDBmwschema' );
00495                 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
00496                 $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
00497                 //$safeschema = $conn->addIdentifierQuotes( $schema );
00498 
00499                 // Check if the user already exists
00500                 $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
00501                 if ( !$userExists ) {
00502                         // Create the user
00503                         try {
00504                                 $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass";
00505 
00506                                 // If the install user is not a superuser, we need to make the install
00507                                 // user a member of the new user's group, so that the install user will
00508                                 // be able to create a schema and other objects on behalf of the new user.
00509                                 if ( !$this->isSuperUser() ) {
00510                                         $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) );
00511                                 }
00512 
00513                                 $conn->query( $sql, __METHOD__ );
00514                         } catch ( DBQueryError $e ) {
00515                                 return Status::newFatal( 'config-install-user-create-failed',
00516                                         $this->getVar( 'wgDBuser' ), $e->getMessage() );
00517                         }
00518                 }
00519 
00520                 return Status::newGood();
00521         }
00522 
00523         function getLocalSettings() {
00524                 $port = $this->getVar( 'wgDBport' );
00525                 $schema = $this->getVar( 'wgDBmwschema' );
00526                 return
00527 "# Postgres specific settings
00528 \$wgDBport           = \"{$port}\";
00529 \$wgDBmwschema       = \"{$schema}\";";
00530         }
00531 
00532         public function preUpgrade() {
00533                 global $wgDBuser, $wgDBpassword;
00534 
00535                 # Normal user and password are selected after this step, so for now
00536                 # just copy these two
00537                 $wgDBuser = $this->getVar( '_InstallUser' );
00538                 $wgDBpassword = $this->getVar( '_InstallPassword' );
00539         }
00540 
00541         public function createTables() {
00542                 $schema = $this->getVar( 'wgDBmwschema' );
00543 
00544                 $status = $this->getConnection();
00545                 if ( !$status->isOK() ) {
00546                         return $status;
00547                 }
00548 
00552                 $conn = $status->value;
00553 
00554                 if( $conn->tableExists( 'user' ) ) {
00555                         $status->warning( 'config-install-tables-exist' );
00556                         $this->enableLB();
00557                         return $status;
00558                 }
00559 
00560                 $conn->begin( __METHOD__ );
00561 
00562                 if( !$conn->schemaExists( $schema ) ) {
00563                         $status->fatal( 'config-install-pg-schema-not-exist' );
00564                         return $status;
00565                 }
00566                 $error = $conn->sourceFile( $conn->getSchemaPath() );
00567                 if( $error !== true ) {
00568                         $conn->reportQueryError( $error, 0, '', __METHOD__ );
00569                         $conn->rollback( __METHOD__ );
00570                         $status->fatal( 'config-install-tables-failed', $error );
00571                 } else {
00572                         $conn->commit( __METHOD__ );
00573                 }
00574                 // Resume normal operations
00575                 if( $status->isOk() ) {
00576                         $this->enableLB();
00577                 }
00578                 return $status;
00579         }
00580 
00581         public function setupPLpgSQL() {
00582                 // Connect as the install user, since it owns the database and so is
00583                 // the user that needs to run "CREATE LANGAUGE"
00584                 $status = $this->getPgConnection( 'create-schema' );
00585                 if ( !$status->isOK() ) {
00586                         return $status;
00587                 }
00591                 $conn = $status->value;
00592 
00593                 $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
00594                         array( 'lanname' => 'plpgsql' ), __METHOD__ );
00595                 if ( $exists ) {
00596                         // Already exists, nothing to do
00597                         return Status::newGood();
00598                 }
00599 
00600                 // plpgsql is not installed, but if we have a pg_pltemplate table, we
00601                 // should be able to create it
00602                 $exists = $conn->selectField(
00603                         array( '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ),
00604                         1,
00605                         array(
00606                                 'pg_namespace.oid=relnamespace',
00607                                 'nspname' => 'pg_catalog',
00608                                 'relname' => 'pg_pltemplate',
00609                         ),
00610                         __METHOD__ );
00611                 if ( $exists ) {
00612                         try {
00613                                 $conn->query( 'CREATE LANGUAGE plpgsql' );
00614                         } catch ( DBQueryError $e ) {
00615                                 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
00616                         }
00617                 } else {
00618                         return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
00619                 }
00620                 return Status::newGood();
00621         }
00622 }