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