PostgresInstaller.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <?php
  2. /**
  3. * PostgreSQL-specific installer.
  4. *
  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.
  9. *
  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.
  14. *
  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
  19. *
  20. * @file
  21. * @ingroup Deployment
  22. */
  23. use Wikimedia\Rdbms\Database;
  24. use Wikimedia\Rdbms\DatabasePostgres;
  25. use Wikimedia\Rdbms\DBQueryError;
  26. use Wikimedia\Rdbms\DBConnectionError;
  27. /**
  28. * Class for setting up the MediaWiki database using Postgres.
  29. *
  30. * @ingroup Deployment
  31. * @since 1.17
  32. */
  33. class PostgresInstaller extends DatabaseInstaller {
  34. protected $globalNames = [
  35. 'wgDBserver',
  36. 'wgDBport',
  37. 'wgDBname',
  38. 'wgDBuser',
  39. 'wgDBpassword',
  40. 'wgDBmwschema',
  41. ];
  42. protected $internalDefaults = [
  43. '_InstallUser' => 'postgres',
  44. ];
  45. public static $minimumVersion = '9.2';
  46. protected static $notMinimumVersionMessage = 'config-postgres-old';
  47. public $maxRoleSearchDepth = 5;
  48. protected $pgConns = [];
  49. function getName() {
  50. return 'postgres';
  51. }
  52. public function isCompiled() {
  53. return self::checkExtension( 'pgsql' );
  54. }
  55. function getConnectForm() {
  56. return $this->getTextBox(
  57. 'wgDBserver',
  58. 'config-db-host',
  59. [],
  60. $this->parent->getHelpBox( 'config-db-host-help' )
  61. ) .
  62. $this->getTextBox( 'wgDBport', 'config-db-port' ) .
  63. Html::openElement( 'fieldset' ) .
  64. Html::element( 'legend', [], wfMessage( 'config-db-wiki-settings' )->text() ) .
  65. $this->getTextBox(
  66. 'wgDBname',
  67. 'config-db-name',
  68. [],
  69. $this->parent->getHelpBox( 'config-db-name-help' )
  70. ) .
  71. $this->getTextBox(
  72. 'wgDBmwschema',
  73. 'config-db-schema',
  74. [],
  75. $this->parent->getHelpBox( 'config-db-schema-help' )
  76. ) .
  77. Html::closeElement( 'fieldset' ) .
  78. $this->getInstallUserBox();
  79. }
  80. function submitConnectForm() {
  81. // Get variables from the request
  82. $newValues = $this->setVarsFromRequest( [
  83. 'wgDBserver',
  84. 'wgDBport',
  85. 'wgDBname',
  86. 'wgDBmwschema'
  87. ] );
  88. // Validate them
  89. $status = Status::newGood();
  90. if ( !strlen( $newValues['wgDBname'] ) ) {
  91. $status->fatal( 'config-missing-db-name' );
  92. } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
  93. $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
  94. }
  95. if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
  96. $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
  97. }
  98. // Submit user box
  99. if ( $status->isOK() ) {
  100. $status->merge( $this->submitInstallUserBox() );
  101. }
  102. if ( !$status->isOK() ) {
  103. return $status;
  104. }
  105. $status = $this->getPgConnection( 'create-db' );
  106. if ( !$status->isOK() ) {
  107. return $status;
  108. }
  109. /**
  110. * @var Database $conn
  111. */
  112. $conn = $status->value;
  113. // Check version
  114. $version = $conn->getServerVersion();
  115. $status = static::meetsMinimumRequirement( $version );
  116. if ( !$status->isOK() ) {
  117. return $status;
  118. }
  119. $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
  120. $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
  121. return Status::newGood();
  122. }
  123. public function getConnection() {
  124. $status = $this->getPgConnection( 'create-tables' );
  125. if ( $status->isOK() ) {
  126. $this->db = $status->value;
  127. }
  128. return $status;
  129. }
  130. public function openConnection() {
  131. return $this->openPgConnection( 'create-tables' );
  132. }
  133. /**
  134. * Open a PG connection with given parameters
  135. * @param string $user User name
  136. * @param string $password
  137. * @param string $dbName Database name
  138. * @param string $schema Database schema
  139. * @return Status
  140. */
  141. protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
  142. $status = Status::newGood();
  143. try {
  144. $db = Database::factory( 'postgres', [
  145. 'host' => $this->getVar( 'wgDBserver' ),
  146. 'port' => $this->getVar( 'wgDBport' ),
  147. 'user' => $user,
  148. 'password' => $password,
  149. 'dbname' => $dbName,
  150. 'schema' => $schema,
  151. 'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ],
  152. ] );
  153. $status->value = $db;
  154. } catch ( DBConnectionError $e ) {
  155. $status->fatal( 'config-connection-error', $e->getMessage() );
  156. }
  157. return $status;
  158. }
  159. /**
  160. * Get a special type of connection
  161. * @param string $type See openPgConnection() for details.
  162. * @return Status
  163. */
  164. protected function getPgConnection( $type ) {
  165. if ( isset( $this->pgConns[$type] ) ) {
  166. return Status::newGood( $this->pgConns[$type] );
  167. }
  168. $status = $this->openPgConnection( $type );
  169. if ( $status->isOK() ) {
  170. /**
  171. * @var Database $conn
  172. */
  173. $conn = $status->value;
  174. $conn->clearFlag( DBO_TRX );
  175. $conn->commit( __METHOD__ );
  176. $this->pgConns[$type] = $conn;
  177. }
  178. return $status;
  179. }
  180. /**
  181. * Get a connection of a specific PostgreSQL-specific type. Connections
  182. * of a given type are cached.
  183. *
  184. * PostgreSQL lacks cross-database operations, so after the new database is
  185. * created, you need to make a separate connection to connect to that
  186. * database and add tables to it.
  187. *
  188. * New tables are owned by the user that creates them, and MediaWiki's
  189. * PostgreSQL support has always assumed that the table owner will be
  190. * $wgDBuser. So before we create new tables, we either need to either
  191. * connect as the other user or to execute a SET ROLE command. Using a
  192. * separate connection for this allows us to avoid accidental cross-module
  193. * dependencies.
  194. *
  195. * @param string $type The type of connection to get:
  196. * - create-db: A connection for creating DBs, suitable for pre-
  197. * installation.
  198. * - create-schema: A connection to the new DB, for creating schemas and
  199. * other similar objects in the new DB.
  200. * - create-tables: A connection with a role suitable for creating tables.
  201. *
  202. * @throws MWException
  203. * @return Status On success, a connection object will be in the value member.
  204. */
  205. protected function openPgConnection( $type ) {
  206. switch ( $type ) {
  207. case 'create-db':
  208. return $this->openConnectionToAnyDB(
  209. $this->getVar( '_InstallUser' ),
  210. $this->getVar( '_InstallPassword' ) );
  211. case 'create-schema':
  212. return $this->openConnectionWithParams(
  213. $this->getVar( '_InstallUser' ),
  214. $this->getVar( '_InstallPassword' ),
  215. $this->getVar( 'wgDBname' ),
  216. $this->getVar( 'wgDBmwschema' ) );
  217. case 'create-tables':
  218. $status = $this->openPgConnection( 'create-schema' );
  219. if ( $status->isOK() ) {
  220. /**
  221. * @var Database $conn
  222. */
  223. $conn = $status->value;
  224. $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
  225. $conn->query( "SET ROLE $safeRole" );
  226. }
  227. return $status;
  228. default:
  229. throw new MWException( "Invalid special connection type: \"$type\"" );
  230. }
  231. }
  232. public function openConnectionToAnyDB( $user, $password ) {
  233. $dbs = [
  234. 'template1',
  235. 'postgres',
  236. ];
  237. if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
  238. array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
  239. }
  240. $conn = false;
  241. $status = Status::newGood();
  242. foreach ( $dbs as $db ) {
  243. try {
  244. $p = [
  245. 'host' => $this->getVar( 'wgDBserver' ),
  246. 'user' => $user,
  247. 'password' => $password,
  248. 'dbname' => $db
  249. ];
  250. $conn = Database::factory( 'postgres', $p );
  251. } catch ( DBConnectionError $error ) {
  252. $conn = false;
  253. $status->fatal( 'config-pg-test-error', $db,
  254. $error->getMessage() );
  255. }
  256. if ( $conn !== false ) {
  257. break;
  258. }
  259. }
  260. if ( $conn !== false ) {
  261. return Status::newGood( $conn );
  262. } else {
  263. return $status;
  264. }
  265. }
  266. protected function getInstallUserPermissions() {
  267. $status = $this->getPgConnection( 'create-db' );
  268. if ( !$status->isOK() ) {
  269. return false;
  270. }
  271. /**
  272. * @var Database $conn
  273. */
  274. $conn = $status->value;
  275. $superuser = $this->getVar( '_InstallUser' );
  276. $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
  277. [ 'rolname' => $superuser ], __METHOD__ );
  278. return $row;
  279. }
  280. protected function canCreateAccounts() {
  281. $perms = $this->getInstallUserPermissions();
  282. if ( !$perms ) {
  283. return false;
  284. }
  285. return $perms->rolsuper === 't' || $perms->rolcreaterole === 't';
  286. }
  287. protected function isSuperUser() {
  288. $perms = $this->getInstallUserPermissions();
  289. if ( !$perms ) {
  290. return false;
  291. }
  292. return $perms->rolsuper === 't';
  293. }
  294. public function getSettingsForm() {
  295. if ( $this->canCreateAccounts() ) {
  296. $noCreateMsg = false;
  297. } else {
  298. $noCreateMsg = 'config-db-web-no-create-privs';
  299. }
  300. $s = $this->getWebUserBox( $noCreateMsg );
  301. return $s;
  302. }
  303. public function submitSettingsForm() {
  304. $status = $this->submitWebUserBox();
  305. if ( !$status->isOK() ) {
  306. return $status;
  307. }
  308. $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
  309. if ( $same ) {
  310. $exists = true;
  311. } else {
  312. // Check if the web user exists
  313. // Connect to the database with the install user
  314. $status = $this->getPgConnection( 'create-db' );
  315. if ( !$status->isOK() ) {
  316. return $status;
  317. }
  318. // @phan-suppress-next-line PhanUndeclaredMethod
  319. $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
  320. }
  321. // Validate the create checkbox
  322. if ( $this->canCreateAccounts() && !$same && !$exists ) {
  323. $create = $this->getVar( '_CreateDBAccount' );
  324. } else {
  325. $this->setVar( '_CreateDBAccount', false );
  326. $create = false;
  327. }
  328. if ( !$create && !$exists ) {
  329. if ( $this->canCreateAccounts() ) {
  330. $msg = 'config-install-user-missing-create';
  331. } else {
  332. $msg = 'config-install-user-missing';
  333. }
  334. return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
  335. }
  336. if ( !$exists ) {
  337. // No more checks to do
  338. return Status::newGood();
  339. }
  340. // Existing web account. Test the connection.
  341. $status = $this->openConnectionToAnyDB(
  342. $this->getVar( 'wgDBuser' ),
  343. $this->getVar( 'wgDBpassword' ) );
  344. if ( !$status->isOK() ) {
  345. return $status;
  346. }
  347. // The web user is conventionally the table owner in PostgreSQL
  348. // installations. Make sure the install user is able to create
  349. // objects on behalf of the web user.
  350. if ( $same || $this->canCreateObjectsForWebUser() ) {
  351. return Status::newGood();
  352. } else {
  353. return Status::newFatal( 'config-pg-not-in-role' );
  354. }
  355. }
  356. /**
  357. * Returns true if the install user is able to create objects owned
  358. * by the web user, false otherwise.
  359. * @return bool
  360. */
  361. protected function canCreateObjectsForWebUser() {
  362. if ( $this->isSuperUser() ) {
  363. return true;
  364. }
  365. $status = $this->getPgConnection( 'create-db' );
  366. if ( !$status->isOK() ) {
  367. return false;
  368. }
  369. $conn = $status->value;
  370. $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
  371. [ 'rolname' => $this->getVar( '_InstallUser' ) ], __METHOD__ );
  372. $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
  373. [ 'rolname' => $this->getVar( 'wgDBuser' ) ], __METHOD__ );
  374. return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth );
  375. }
  376. /**
  377. * Recursive helper for canCreateObjectsForWebUser().
  378. * @param Database $conn
  379. * @param int $targetMember Role ID of the member to look for
  380. * @param int $group Role ID of the group to look for
  381. * @param int $maxDepth Maximum recursive search depth
  382. * @return bool
  383. */
  384. protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
  385. if ( $targetMember === $group ) {
  386. // A role is always a member of itself
  387. return true;
  388. }
  389. // Get all members of the given group
  390. $res = $conn->select( '"pg_catalog"."pg_auth_members"', [ 'member' ],
  391. [ 'roleid' => $group ], __METHOD__ );
  392. foreach ( $res as $row ) {
  393. if ( $row->member == $targetMember ) {
  394. // Found target member
  395. return true;
  396. }
  397. // Recursively search each member of the group to see if the target
  398. // is a member of it, up to the given maximum depth.
  399. if ( $maxDepth > 0 &&
  400. $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 )
  401. ) {
  402. // Found member of member
  403. return true;
  404. }
  405. }
  406. return false;
  407. }
  408. public function preInstall() {
  409. $createDbAccount = [
  410. 'name' => 'user',
  411. 'callback' => [ $this, 'setupUser' ],
  412. ];
  413. $commitCB = [
  414. 'name' => 'pg-commit',
  415. 'callback' => [ $this, 'commitChanges' ],
  416. ];
  417. $plpgCB = [
  418. 'name' => 'pg-plpgsql',
  419. 'callback' => [ $this, 'setupPLpgSQL' ],
  420. ];
  421. $schemaCB = [
  422. 'name' => 'schema',
  423. 'callback' => [ $this, 'setupSchema' ]
  424. ];
  425. if ( $this->getVar( '_CreateDBAccount' ) ) {
  426. $this->parent->addInstallStep( $createDbAccount, 'database' );
  427. }
  428. $this->parent->addInstallStep( $commitCB, 'interwiki' );
  429. $this->parent->addInstallStep( $plpgCB, 'database' );
  430. $this->parent->addInstallStep( $schemaCB, 'database' );
  431. }
  432. function setupDatabase() {
  433. $status = $this->getPgConnection( 'create-db' );
  434. if ( !$status->isOK() ) {
  435. return $status;
  436. }
  437. $conn = $status->value;
  438. $dbName = $this->getVar( 'wgDBname' );
  439. $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
  440. [ 'datname' => $dbName ], __METHOD__ );
  441. if ( !$exists ) {
  442. $safedb = $conn->addIdentifierQuotes( $dbName );
  443. $conn->query( "CREATE DATABASE $safedb", __METHOD__ );
  444. }
  445. return Status::newGood();
  446. }
  447. function setupSchema() {
  448. // Get a connection to the target database
  449. $status = $this->getPgConnection( 'create-schema' );
  450. if ( !$status->isOK() ) {
  451. return $status;
  452. }
  453. /** @var DatabasePostgres $conn */
  454. $conn = $status->value;
  455. '@phan-var DatabasePostgres $conn';
  456. // Create the schema if necessary
  457. $schema = $this->getVar( 'wgDBmwschema' );
  458. $safeschema = $conn->addIdentifierQuotes( $schema );
  459. $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
  460. if ( !$conn->schemaExists( $schema ) ) {
  461. try {
  462. $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
  463. } catch ( DBQueryError $e ) {
  464. return Status::newFatal( 'config-install-pg-schema-failed',
  465. $this->getVar( '_InstallUser' ), $schema );
  466. }
  467. }
  468. // Select the new schema in the current connection
  469. $conn->determineCoreSchema( $schema );
  470. return Status::newGood();
  471. }
  472. function commitChanges() {
  473. $this->db->commit( __METHOD__ );
  474. return Status::newGood();
  475. }
  476. function setupUser() {
  477. if ( !$this->getVar( '_CreateDBAccount' ) ) {
  478. return Status::newGood();
  479. }
  480. $status = $this->getPgConnection( 'create-db' );
  481. if ( !$status->isOK() ) {
  482. return $status;
  483. }
  484. /** @var DatabasePostgres $conn */
  485. $conn = $status->value;
  486. '@phan-var DatabasePostgres $conn';
  487. $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
  488. $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
  489. // Check if the user already exists
  490. $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
  491. if ( !$userExists ) {
  492. // Create the user
  493. try {
  494. $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass";
  495. // If the install user is not a superuser, we need to make the install
  496. // user a member of the new user's group, so that the install user will
  497. // be able to create a schema and other objects on behalf of the new user.
  498. if ( !$this->isSuperUser() ) {
  499. $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) );
  500. }
  501. $conn->query( $sql, __METHOD__ );
  502. } catch ( DBQueryError $e ) {
  503. return Status::newFatal( 'config-install-user-create-failed',
  504. $this->getVar( 'wgDBuser' ), $e->getMessage() );
  505. }
  506. }
  507. return Status::newGood();
  508. }
  509. function getLocalSettings() {
  510. $port = $this->getVar( 'wgDBport' );
  511. $schema = $this->getVar( 'wgDBmwschema' );
  512. return "# Postgres specific settings
  513. \$wgDBport = \"{$port}\";
  514. \$wgDBmwschema = \"{$schema}\";";
  515. }
  516. public function preUpgrade() {
  517. global $wgDBuser, $wgDBpassword;
  518. # Normal user and password are selected after this step, so for now
  519. # just copy these two
  520. $wgDBuser = $this->getVar( '_InstallUser' );
  521. $wgDBpassword = $this->getVar( '_InstallPassword' );
  522. }
  523. public function createTables() {
  524. $schema = $this->getVar( 'wgDBmwschema' );
  525. $status = $this->getConnection();
  526. if ( !$status->isOK() ) {
  527. return $status;
  528. }
  529. /** @var DatabasePostgres $conn */
  530. $conn = $status->value;
  531. '@phan-var DatabasePostgres $conn';
  532. if ( $conn->tableExists( 'archive' ) ) {
  533. $status->warning( 'config-install-tables-exist' );
  534. $this->enableLB();
  535. return $status;
  536. }
  537. $conn->begin( __METHOD__ );
  538. if ( !$conn->schemaExists( $schema ) ) {
  539. $status->fatal( 'config-install-pg-schema-not-exist' );
  540. return $status;
  541. }
  542. $error = $conn->sourceFile( $this->getSchemaPath( $conn ) );
  543. if ( $error !== true ) {
  544. $conn->reportQueryError( $error, 0, '', __METHOD__ );
  545. $conn->rollback( __METHOD__ );
  546. $status->fatal( 'config-install-tables-failed', $error );
  547. } else {
  548. $conn->commit( __METHOD__ );
  549. }
  550. // Resume normal operations
  551. if ( $status->isOK() ) {
  552. $this->enableLB();
  553. }
  554. return $status;
  555. }
  556. public function getGlobalDefaults() {
  557. // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
  558. // the use of a schema, so we need to set it here
  559. return array_merge( parent::getGlobalDefaults(), [
  560. 'wgDBmwschema' => 'mediawiki',
  561. ] );
  562. }
  563. public function setupPLpgSQL() {
  564. // Connect as the install user, since it owns the database and so is
  565. // the user that needs to run "CREATE LANGUAGE"
  566. $status = $this->getPgConnection( 'create-schema' );
  567. if ( !$status->isOK() ) {
  568. return $status;
  569. }
  570. /**
  571. * @var Database $conn
  572. */
  573. $conn = $status->value;
  574. $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
  575. [ 'lanname' => 'plpgsql' ], __METHOD__ );
  576. if ( $exists ) {
  577. // Already exists, nothing to do
  578. return Status::newGood();
  579. }
  580. // plpgsql is not installed, but if we have a pg_pltemplate table, we
  581. // should be able to create it
  582. $exists = $conn->selectField(
  583. [ '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ],
  584. 1,
  585. [
  586. 'pg_namespace.oid=relnamespace',
  587. 'nspname' => 'pg_catalog',
  588. 'relname' => 'pg_pltemplate',
  589. ],
  590. __METHOD__ );
  591. if ( $exists ) {
  592. try {
  593. $conn->query( 'CREATE LANGUAGE plpgsql' );
  594. } catch ( DBQueryError $e ) {
  595. return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
  596. }
  597. } else {
  598. return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
  599. }
  600. return Status::newGood();
  601. }
  602. }