]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/db/DatabasePostgres.php
MediaWiki 1.15.0
[autoinstallsdev/mediawiki.git] / includes / db / DatabasePostgres.php
1 <?php
2 /**
3  * @ingroup Database
4  * @file
5  * This is the Postgres database abstraction layer.
6  *
7  */
8 class PostgresField {
9         private $name, $tablename, $type, $nullable, $max_length;
10
11         static function fromText($db, $table, $field) {
12         global $wgDBmwschema;
13
14                 $q = <<<END
15 SELECT
16 CASE WHEN typname = 'int2' THEN 'smallint'
17 WHEN typname = 'int4' THEN 'integer'
18 WHEN typname = 'int8' THEN 'bigint'
19 WHEN typname = 'bpchar' THEN 'char'
20 ELSE typname END AS typname,
21 attnotnull, attlen
22 FROM pg_class, pg_namespace, pg_attribute, pg_type
23 WHERE relnamespace=pg_namespace.oid
24 AND relkind='r'
25 AND attrelid=pg_class.oid
26 AND atttypid=pg_type.oid
27 AND nspname=%s
28 AND relname=%s
29 AND attname=%s;
30 END;
31                 $res = $db->query(sprintf($q,
32                                 $db->addQuotes($wgDBmwschema),
33                                 $db->addQuotes($table),
34                                 $db->addQuotes($field)));
35                 $row = $db->fetchObject($res);
36                 if (!$row)
37                         return null;
38                 $n = new PostgresField;
39                 $n->type = $row->typname;
40                 $n->nullable = ($row->attnotnull == 'f');
41                 $n->name = $field;
42                 $n->tablename = $table;
43                 $n->max_length = $row->attlen;
44                 return $n;
45         }
46
47         function name() {
48                 return $this->name;
49         }
50
51         function tableName() {
52                 return $this->tablename;
53         }
54
55         function type() {
56                 return $this->type;
57         }
58
59         function nullable() {
60                 return $this->nullable;
61         }
62
63         function maxLength() {
64                 return $this->max_length;
65         }
66 }
67
68 /**
69  * @ingroup Database
70  */
71 class DatabasePostgres extends Database {
72         var $mInsertId = NULL;
73         var $mLastResult = NULL;
74         var $numeric_version = NULL;
75         var $mAffectedRows = NULL;
76
77         function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false,
78                 $failFunction = false, $flags = 0 )
79         {
80
81                 $this->mFailFunction = $failFunction;
82                 $this->mFlags = $flags;
83                 $this->open( $server, $user, $password, $dbName);
84
85         }
86
87         function cascadingDeletes() {
88                 return true;
89         }
90         function cleanupTriggers() {
91                 return true;
92         }
93         function strictIPs() {
94                 return true;
95         }
96         function realTimestamps() {
97                 return true;
98         }
99         function implicitGroupby() {
100                 return false;
101         }
102         function implicitOrderby() {
103                 return false;
104         }
105         function searchableIPs() {
106                 return true;
107         }
108         function functionalIndexes() {
109                 return true;
110         }
111
112         function hasConstraint( $name ) {
113                 global $wgDBmwschema;
114                 $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" . pg_escape_string( $name ) . "' AND n.nspname = '" . pg_escape_string($wgDBmwschema) ."'";
115                 return $this->numRows($res = $this->doQuery($SQL));
116         }
117
118         static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
119         {
120                 return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags );
121         }
122
123         /**
124          * Usually aborts on failure
125          * If the failFunction is set to a non-zero integer, returns success
126          */
127         function open( $server, $user, $password, $dbName ) {
128                 # Test for Postgres support, to avoid suppressed fatal error
129                 if ( !function_exists( 'pg_connect' ) ) {
130                         throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
131                 }
132
133                 global $wgDBport;
134
135                 if (!strlen($user)) { ## e.g. the class is being loaded  
136                         return;  
137                 }
138                 $this->close();
139                 $this->mServer = $server;
140                 $this->mPort = $port = $wgDBport;
141                 $this->mUser = $user;
142                 $this->mPassword = $password;
143                 $this->mDBname = $dbName;
144
145                 $connectVars = array(
146                         'dbname' => $dbName,
147                         'user' => $user,
148                         'password' => $password );
149                 if ($server!=false && $server!="") {
150                         $connectVars['host'] = $server;
151                 }
152                 if ($port!=false && $port!="") {
153                         $connectVars['port'] = $port;
154                 }
155                 $connectString = $this->makeConnectionString( $connectVars );
156
157                 $this->installErrorHandler();
158                 $this->mConn = pg_connect( $connectString );
159                 $phpError = $this->restoreErrorHandler();
160
161                 if ( $this->mConn == false ) {
162                         wfDebug( "DB connection error\n" );
163                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
164                         wfDebug( $this->lastError()."\n" );
165                         if ( !$this->mFailFunction ) {
166                                 throw new DBConnectionError( $this, $phpError );
167                         } else {
168                                 return false;
169                         }
170                 }
171
172                 $this->mOpened = true;
173
174                 global $wgCommandLineMode;
175                 ## If called from the command-line (e.g. importDump), only show errors
176                 if ($wgCommandLineMode) {
177                         $this->doQuery( "SET client_min_messages = 'ERROR'" );
178                 }
179
180                 $this->doQuery( "SET client_encoding='UTF8'" );
181
182                 global $wgDBmwschema, $wgDBts2schema;
183                 if (isset( $wgDBmwschema ) && isset( $wgDBts2schema )
184                         && $wgDBmwschema !== 'mediawiki'
185                         && preg_match( '/^\w+$/', $wgDBmwschema )
186                         && preg_match( '/^\w+$/', $wgDBts2schema )
187                 ) {
188                         $safeschema = $this->quote_ident($wgDBmwschema);
189                         $safeschema2 = $this->quote_ident($wgDBts2schema);
190                         $this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" );
191                 }
192
193                 return $this->mConn;
194         }
195
196         function makeConnectionString( $vars ) {
197                 $s = '';
198                 foreach ( $vars as $name => $value ) {
199                         $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
200                 }
201                 return $s;
202         }
203
204
205         function initial_setup($password, $dbName) {
206                 // If this is the initial connection, setup the schema stuff and possibly create the user
207                 global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema;
208
209                 print "<li>Checking the version of Postgres...";
210                 $version = $this->getServerVersion();
211                 $PGMINVER = '8.1';
212                 if ($version < $PGMINVER) {
213                         print "<b>FAILED</b>. Required version is $PGMINVER. You have " . htmlspecialchars( $version ) . "</li>\n";
214                         dieout("</ul>");
215                 }
216                 print "version " . htmlspecialchars( $this->numeric_version ) . " is OK.</li>\n";
217
218                 $safeuser = $this->quote_ident($wgDBuser);
219                 // Are we connecting as a superuser for the first time?
220                 if ($wgDBsuperuser) {
221                         // Are we really a superuser? Check out our rights
222                         $SQL = "SELECT
223                       CASE WHEN usesuper IS TRUE THEN
224                       CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
225                       ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
226                     END AS rights
227                     FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser);
228                         $rows = $this->numRows($res = $this->doQuery($SQL));
229                         if (!$rows) {
230                                 print "<li>ERROR: Could not read permissions for user \"" . htmlspecialchars( $wgDBsuperuser ) . "\"</li>\n";
231                                 dieout('</ul>');
232                         }
233                         $perms = pg_fetch_result($res, 0, 0);
234
235                         $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser);
236                         $rows = $this->numRows($this->doQuery($SQL));
237                         if ($rows) {
238                                 print "<li>User \"" . htmlspecialchars( $wgDBuser ) . "\" already exists, skipping account creation.</li>";
239                         }
240                         else {
241                                 if ($perms != 1 and $perms != 3) {
242                                         print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create other users. ";
243                                         print 'Please use a different Postgres user.</li>';
244                                         dieout('</ul>');
245                                 }
246                                 print "<li>Creating user <b>" . htmlspecialchars( $wgDBuser ) . "</b>...";
247                                 $safepass = $this->addQuotes($wgDBpassword);
248                                 $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass";
249                                 $this->doQuery($SQL);
250                                 print "OK</li>\n";
251                         }
252                         // User now exists, check out the database
253                         if ($dbName != $wgDBname) {
254                                 $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname);
255                                 $rows = $this->numRows($this->doQuery($SQL));
256                                 if ($rows) {
257                                         print "<li>Database \"" . htmlspecialchars( $wgDBname ) . "\" already exists, skipping database creation.</li>";
258                                 }
259                                 else {
260                                         if ($perms < 1) {
261                                                 print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create databases. ";
262                                                 print 'Please use a different Postgres user.</li>';
263                                                 dieout('</ul>');
264                                         }
265                                         print "<li>Creating database <b>" . htmlspecialchars( $wgDBname ) . "</b>...";
266                                         $safename = $this->quote_ident($wgDBname);
267                                         $SQL = "CREATE DATABASE $safename OWNER $safeuser ";
268                                         $this->doQuery($SQL);
269                                         print "OK</li>\n";
270                                         // Hopefully tsearch2 and plpgsql are in template1...
271                                 }
272
273                                 // Reconnect to check out tsearch2 rights for this user
274                                 print "<li>Connecting to \"" . htmlspecialchars( $wgDBname ) . "\" as superuser \"" .
275                                         htmlspecialchars( $wgDBsuperuser ) . "\" to check rights...";
276
277                                 $connectVars = array();
278                                 if ($this->mServer!=false && $this->mServer!="") {
279                                         $connectVars['host'] = $this->mServer;
280                                 }
281                                 if ($this->mPort!=false && $this->mPort!="") {
282                                         $connectVars['port'] = $this->mPort;
283                                 }
284                                 $connectVars['dbname'] = $wgDBname;
285                                 $connectVars['user'] = $wgDBsuperuser;
286                                 $connectVars['password'] = $password;
287
288                                 @$this->mConn = pg_connect( $this->makeConnectionString( $connectVars ) );
289                                 if ( $this->mConn == false ) {
290                                         print "<b>FAILED TO CONNECT!</b></li>";
291                                         dieout("</ul>");
292                                 }
293                                 print "OK</li>\n";
294                         }
295
296                         if ($this->numeric_version < 8.3) {
297                                 // Tsearch2 checks
298                                 print "<li>Checking that tsearch2 is installed in the database \"" . 
299                                         htmlspecialchars( $wgDBname ) . "\"...";
300                                 if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) {
301                                         print "<b>FAILED</b>. tsearch2 must be installed in the database \"" . 
302                                                 htmlspecialchars( $wgDBname ) . "\".";
303                                         print "Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
304                                         print " for instructions or ask on #postgresql on irc.freenode.net</li>\n";
305                                         dieout("</ul>");
306                                 }
307                                 print "OK</li>\n";
308                                 print "<li>Ensuring that user \"" . htmlspecialchars( $wgDBuser ) . 
309                                         "\" has select rights on the tsearch2 tables...";
310                                 foreach (array('cfg','cfgmap','dict','parser') as $table) {
311                                         $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser";
312                                         $this->doQuery($SQL);
313                                 }
314                                 print "OK</li>\n";
315                         }
316
317                         // Setup the schema for this user if needed
318                         $result = $this->schemaExists($wgDBmwschema);
319                         $safeschema = $this->quote_ident($wgDBmwschema);
320                         if (!$result) {
321                                 print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
322                                 $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser");
323                                 if (!$result) {
324                                         print "<b>FAILED</b>.</li>\n";
325                                         dieout("</ul>");
326                                 }
327                                 print "OK</li>\n";
328                         }
329                         else {
330                                 print "<li>Schema already exists, explicitly granting rights...\n";
331                                 $safeschema2 = $this->addQuotes($wgDBmwschema);
332                                 $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
333                                         "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n".
334                                         "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n".
335                                         "AND p.relkind IN ('r','S','v')\n";
336                                 $SQL .= "UNION\n";
337                                 $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n".
338                                         "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n".
339                                         "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n".
340                                         "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2";
341                                 $res = $this->doQuery($SQL);
342                                 if (!$res) {
343                                         print "<b>FAILED</b>. Could not set rights for the user.</li>\n";
344                                         dieout("</ul>");
345                                 }
346                                 $this->doQuery("SET search_path = $safeschema");
347                                 $rows = $this->numRows($res);
348                                 while ($rows) {
349                                         $rows--;
350                                         $this->doQuery(pg_fetch_result($res, $rows, 0));
351                                 }
352                                 print "OK</li>";
353                         }
354
355                         // Install plpgsql if needed
356                         $this->setup_plpgsql();
357
358                         $wgDBsuperuser = '';
359                         return true; // Reconnect as regular user
360
361                 } // end superuser
362
363                 if (!defined('POSTGRES_SEARCHPATH')) {
364
365                         if ($this->numeric_version < 8.3) {
366                                 // Do we have the basic tsearch2 table?
367                                 print "<li>Checking for tsearch2 in the schema \"" . htmlspecialchars( $wgDBts2schema ) . "\"...";
368                                 if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) {
369                                         print "<b>FAILED</b>. Make sure tsearch2 is installed. See <a href=";
370                                         print "'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
371                                         print " for instructions.</li>\n";
372                                         dieout("</ul>");
373                                 }
374                                 print "OK</li>\n";
375
376                                 // Does this user have the rights to the tsearch2 tables?
377                                 $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
378                                 print "<li>Checking tsearch2 permissions...";
379                                 // Let's check all four, just to be safe
380                                 error_reporting( 0 );
381                                 $ts2tables = array('cfg','cfgmap','dict','parser');
382                                 $safetsschema = $this->quote_ident($wgDBts2schema);
383                                 foreach ( $ts2tables AS $tname ) {
384                                         $SQL = "SELECT count(*) FROM $safetsschema.pg_ts_$tname";
385                                         $res = $this->doQuery($SQL);
386                                         if (!$res) {
387                                                 print "<b>FAILED</b> to access " . htmlspecialchars( "pg_ts_$tname" ) . 
388                                                         ". Make sure that the user \"". htmlspecialchars( $wgDBuser ) . 
389                                                         "\" has SELECT access to all four tsearch2 tables</li>\n";
390                                                 dieout("</ul>");
391                                         }
392                                 }
393                                 $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = " . $this->addQuotes( $ctype ) ;
394                                 $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END";
395                                 $res = $this->doQuery($SQL);
396                                 error_reporting( E_ALL );
397                                 if (!$res) {
398                                         print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n";
399                                         dieout("</ul>");
400                                 }
401                                 print "OK</li>";
402
403                                 // Will the current locale work? Can we force it to?
404                                 print "<li>Verifying tsearch2 locale with " . htmlspecialchars( $ctype ) . "...";
405                                 $rows = $this->numRows($res);
406                                 $resetlocale = 0;
407                                 if (!$rows) {
408                                         print "<b>not found</b></li>\n";
409                                         print "<li>Attempting to set default tsearch2 locale to \"" . htmlspecialchars( $ctype ) . "\"...";
410                                         $resetlocale = 1;
411                                 }
412                                 else {
413                                         $tsname = pg_fetch_result($res, 0, 0);
414                                         if ($tsname != 'default') {
415                                                 print "<b>not set to default (" . htmlspecialchars( $tsname ) . ")</b>";
416                                                 print "<li>Attempting to change tsearch2 default locale to \"" . 
417                                                         htmlspecialchars( $ctype ) . "\"...";
418                                                 $resetlocale = 1;
419                                         }
420                                 }
421                                 if ($resetlocale) {
422                                         $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = " . $this->addQuotes( $ctype ) . " WHERE ts_name = 'default'";
423                                         $res = $this->doQuery($SQL);
424                                         if (!$res) {
425                                                 print "<b>FAILED</b>. ";
426                                                 print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"" . 
427                                                         htmlspecialchars( $ctype ) . "\"</li>\n";
428                                                 dieout("</ul>");
429                                         }
430                                         print "OK</li>";
431                                 }
432
433                                 // Final test: try out a simple tsearch2 query
434                                 $SQL = "SELECT $safetsschema.to_tsvector('default','MediaWiki tsearch2 testing')";
435                                 $res = $this->doQuery($SQL);
436                                 if (!$res) {
437                                         print "<b>FAILED</b>. Specifically, \"" . htmlspecialchars( $SQL ) . "\" did not work.</li>";
438                                         dieout("</ul>");
439                                 }
440                                 print "OK</li>";
441                         }
442
443                         // Install plpgsql if needed
444                         $this->setup_plpgsql();
445
446                         // Does the schema already exist? Who owns it?
447                         $result = $this->schemaExists($wgDBmwschema);
448                         if (!$result) {
449                                 print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
450                                 error_reporting( 0 );
451                                 $safeschema = $this->quote_ident($wgDBmwschema);
452                                 $result = $this->doQuery("CREATE SCHEMA $safeschema");
453                                 error_reporting( E_ALL );
454                                 if (!$result) {
455                                         print "<b>FAILED</b>. The user \"" . htmlspecialchars( $wgDBuser ) . 
456                                                 "\" must be able to access the schema. ".
457                                                 "You can try making them the owner of the database, or try creating the schema with a ".
458                                                 "different user, and then grant access to the \"" . 
459                                                 htmlspecialchars( $wgDBuser ) . "\" user.</li>\n";
460                                         dieout("</ul>");
461                                 }
462                                 print "OK</li>\n";
463                         }
464                         else if ($result != $wgDBuser) {
465                                 print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists but is not owned by \"" . 
466                                         htmlspecialchars( $wgDBuser ) . "\". Not ideal.</li>\n";
467                         }
468                         else {
469                                 print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists and is owned by \"" . 
470                                         htmlspecialchars( $wgDBuser ) . "\". Excellent.</li>\n";
471                         }
472
473                         // Always return GMT time to accomodate the existing integer-based timestamp assumption
474                         print "<li>Setting the timezone to GMT for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
475                         $SQL = "ALTER USER $safeuser SET timezone = 'GMT'";
476                         $result = pg_query($this->mConn, $SQL);
477                         if (!$result) {
478                                 print "<b>FAILED</b>.</li>\n";
479                                 dieout("</ul>");
480                         }
481                         print "OK</li>\n";
482                         // Set for the rest of this session
483                         $SQL = "SET timezone = 'GMT'";
484                         $result = pg_query($this->mConn, $SQL);
485                         if (!$result) {
486                                 print "<li>Failed to set timezone</li>\n";
487                                 dieout("</ul>");
488                         }
489
490                         print "<li>Setting the datestyle to ISO, YMD for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
491                         $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'";
492                         $result = pg_query($this->mConn, $SQL);
493                         if (!$result) {
494                                 print "<b>FAILED</b>.</li>\n";
495                                 dieout("</ul>");
496                         }
497                         print "OK</li>\n";
498                         // Set for the rest of this session
499                         $SQL = "SET datestyle = 'ISO, YMD'";
500                         $result = pg_query($this->mConn, $SQL);
501                         if (!$result) {
502                                 print "<li>Failed to set datestyle</li>\n";
503                                 dieout("</ul>");
504                         }
505
506                         // Fix up the search paths if needed
507                         print "<li>Setting the search path for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
508                         $path = $this->quote_ident($wgDBmwschema);
509                         if ($wgDBts2schema !== $wgDBmwschema)
510                                 $path .= ", ". $this->quote_ident($wgDBts2schema);
511                         if ($wgDBmwschema !== 'public' and $wgDBts2schema !== 'public')
512                                 $path .= ", public";
513                         $SQL = "ALTER USER $safeuser SET search_path = $path";
514                         $result = pg_query($this->mConn, $SQL);
515                         if (!$result) {
516                                 print "<b>FAILED</b>.</li>\n";
517                                 dieout("</ul>");
518                         }
519                         print "OK</li>\n";
520                         // Set for the rest of this session
521                         $SQL = "SET search_path = $path";
522                         $result = pg_query($this->mConn, $SQL);
523                         if (!$result) {
524                                 print "<li>Failed to set search_path</li>\n";
525                                 dieout("</ul>");
526                         }
527                         define( "POSTGRES_SEARCHPATH", $path );
528                 }
529         }
530
531
532         function setup_plpgsql() {
533                 print "<li>Checking for Pl/Pgsql ...";
534                 $SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'";
535                 $rows = $this->numRows($this->doQuery($SQL));
536                 if ($rows < 1) {
537                         // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
538                         print "not installed. Attempting to install Pl/Pgsql ...";
539                         $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
540                                 "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'";
541                         $rows = $this->numRows($this->doQuery($SQL));
542                         if ($rows >= 1) {
543                         $olde = error_reporting(0);
544                                 error_reporting($olde - E_WARNING);
545                                 $result = $this->doQuery("CREATE LANGUAGE plpgsql");
546                                 error_reporting($olde);
547                                 if (!$result) {
548                                         print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" . 
549                                                 htmlspecialchars( $wgDBname ) . "</tt></li>";
550                                         dieout("</ul>");
551                                 }
552                         }
553                         else {
554                                 print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" . 
555                                         htmlspecialchars( $wgDBname ) . "</tt></li>";
556                                 dieout("</ul>");
557                         }
558                 }
559                 print "OK</li>\n";
560         }
561
562
563         /**
564          * Closes a database connection, if it is open
565          * Returns success, true if already closed
566          */
567         function close() {
568                 $this->mOpened = false;
569                 if ( $this->mConn ) {
570                         return pg_close( $this->mConn );
571                 } else {
572                         return true;
573                 }
574         }
575
576         function doQuery( $sql ) {
577                 if (function_exists('mb_convert_encoding')) {
578                         $sql = mb_convert_encoding($sql,'UTF-8');
579                 }
580                 $this->mLastResult = pg_query( $this->mConn, $sql);
581                 $this->mAffectedRows = NULL; // use pg_affected_rows(mLastResult)
582                 return $this->mLastResult;
583         }
584
585         function queryIgnore( $sql, $fname = '' ) {
586                 return $this->query( $sql, $fname, true );
587         }
588
589         function freeResult( $res ) {
590                 if ( $res instanceof ResultWrapper ) {
591                         $res = $res->result;
592                 }
593                 if ( !@pg_free_result( $res ) ) {
594                         throw new DBUnexpectedError($this,  "Unable to free Postgres result\n" );
595                 }
596         }
597
598         function fetchObject( $res ) {
599                 if ( $res instanceof ResultWrapper ) {
600                         $res = $res->result;
601                 }
602                 @$row = pg_fetch_object( $res );
603                 # FIXME: HACK HACK HACK HACK debug
604
605                 # TODO:
606                 # hashar : not sure if the following test really trigger if the object
607                 #          fetching failed.
608                 if( pg_last_error($this->mConn) ) {
609                         throw new DBUnexpectedError($this,  'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
610                 }
611                 return $row;
612         }
613
614         function fetchRow( $res ) {
615                 if ( $res instanceof ResultWrapper ) {
616                         $res = $res->result;
617                 }
618                 @$row = pg_fetch_array( $res );
619                 if( pg_last_error($this->mConn) ) {
620                         throw new DBUnexpectedError($this,  'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
621                 }
622                 return $row;
623         }
624
625         function numRows( $res ) {
626                 if ( $res instanceof ResultWrapper ) {
627                         $res = $res->result;
628                 }
629                 @$n = pg_num_rows( $res );
630                 if( pg_last_error($this->mConn) ) {
631                         throw new DBUnexpectedError($this,  'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
632                 }
633                 return $n;
634         }
635         function numFields( $res ) {
636                 if ( $res instanceof ResultWrapper ) {
637                         $res = $res->result;
638                 }
639                 return pg_num_fields( $res );
640         }
641         function fieldName( $res, $n ) {
642                 if ( $res instanceof ResultWrapper ) {
643                         $res = $res->result;
644                 }
645                 return pg_field_name( $res, $n );
646         }
647
648         /**
649          * This must be called after nextSequenceVal
650          */
651         function insertId() {
652                 return $this->mInsertId;
653         }
654
655         function dataSeek( $res, $row ) {
656                 if ( $res instanceof ResultWrapper ) {
657                         $res = $res->result;
658                 }
659                 return pg_result_seek( $res, $row );
660         }
661
662         function lastError() {
663                 if ( $this->mConn ) {
664                         return pg_last_error();
665                 }
666                 else {
667                         return "No database connection";
668                 }
669         }
670         function lastErrno() {
671                 return pg_last_error() ? 1 : 0;
672         }
673
674         function affectedRows() {
675                 if ( !is_null( $this->mAffectedRows ) ) {
676                         // Forced result for simulated queries
677                         return $this->mAffectedRows;
678                 }
679                 if( empty( $this->mLastResult ) )
680                         return 0;
681                 return pg_affected_rows( $this->mLastResult );
682         }
683
684         /**
685          * Estimate rows in dataset
686          * Returns estimated count, based on EXPLAIN output
687          * This is not necessarily an accurate estimate, so use sparingly
688          * Returns -1 if count cannot be found
689          * Takes same arguments as Database::select()
690          */
691
692         function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
693                 $options['EXPLAIN'] = true;
694                 $res = $this->select( $table, $vars, $conds, $fname, $options );
695                 $rows = -1;
696                 if ( $res ) {
697                         $row = $this->fetchRow( $res );
698                         $count = array();
699                         if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
700                                 $rows = $count[1];
701                         }
702                         $this->freeResult($res);
703                 }
704                 return $rows;
705         }
706
707
708         /**
709          * Returns information about an index
710          * If errors are explicitly ignored, returns NULL on failure
711          */
712         function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) {
713                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
714                 $res = $this->query( $sql, $fname );
715                 if ( !$res ) {
716                         return NULL;
717                 }
718                 while ( $row = $this->fetchObject( $res ) ) {
719                         if ( $row->indexname == $this->indexName( $index ) ) {
720                                 return $row;
721                         }
722                 }
723                 return false;
724         }
725
726         function indexUnique ($table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
727                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
728                         " AND indexdef LIKE 'CREATE UNIQUE%(" . 
729                         $this->strencode( $this->indexName( $index ) ) .
730                         ")'";
731                 $res = $this->query( $sql, $fname );
732                 if ( !$res )
733                         return NULL;
734                 while ($row = $this->fetchObject( $res ))
735                         return true;
736                 return false;
737
738         }
739
740         /**
741          * INSERT wrapper, inserts an array into a table
742          *
743          * $args may be a single associative array, or an array of these with numeric keys,
744          * for multi-row insert (Postgres version 8.2 and above only).
745          *
746          * @param $table   String: Name of the table to insert to.
747          * @param $args    Array: Items to insert into the table.
748          * @param $fname   String: Name of the function, for profiling
749          * @param $options String or Array. Valid options: IGNORE
750          *
751          * @return bool Success of insert operation. IGNORE always returns true.
752          */
753         function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
754                 global $wgDBversion;
755
756                 if ( !count( $args ) ) {
757                         return true;
758                 }
759
760                 $table = $this->tableName( $table );
761                 if (! isset( $wgDBversion ) ) {
762                         $wgDBversion = $this->getServerVersion();
763                 }
764
765                 if ( !is_array( $options ) )
766                         $options = array( $options );
767
768                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
769                         $multi = true;
770                         $keys = array_keys( $args[0] );
771                 }
772                 else {
773                         $multi = false;
774                         $keys = array_keys( $args );
775                 }
776
777                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
778                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
779
780                 // If we are not in a transaction, we need to be for savepoint trickery
781                 $didbegin = 0;
782                 if ( $ignore ) {
783                         if (! $this->mTrxLevel) {
784                                 $this->begin();
785                                 $didbegin = 1;
786                         }
787                         $olde = error_reporting( 0 );
788                         // For future use, we may want to track the number of actual inserts
789                         // Right now, insert (all writes) simply return true/false
790                         $numrowsinserted = 0;
791                 }
792
793                 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
794
795                 if ( $multi ) {
796                         if ( $wgDBversion >= 8.2 && !$ignore ) {
797                                 $first = true;
798                                 foreach ( $args as $row ) {
799                                         if ( $first ) {
800                                                 $first = false;
801                                         } else {
802                                                 $sql .= ',';
803                                         }
804                                         $sql .= '(' . $this->makeList( $row ) . ')';
805                                 }
806                                 $res = (bool)$this->query( $sql, $fname, $ignore );
807                         }
808                         else {
809                                 $res = true;
810                                 $origsql = $sql;
811                                 foreach ( $args as $row ) {
812                                         $tempsql = $origsql;
813                                         $tempsql .= '(' . $this->makeList( $row ) . ')';
814
815                                         if ( $ignore ) {
816                                                 pg_query($this->mConn, "SAVEPOINT $ignore");
817                                         }
818
819                                         $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
820
821                                         if ( $ignore ) {
822                                                 $bar = pg_last_error();
823                                                 if ($bar != false) {
824                                                         pg_query( $this->mConn, "ROLLBACK TO $ignore" );
825                                                 }
826                                                 else {
827                                                         pg_query( $this->mConn, "RELEASE $ignore" );
828                                                         $numrowsinserted++;
829                                                 }
830                                         }
831
832                                         // If any of them fail, we fail overall for this function call
833                                         // Note that this will be ignored if IGNORE is set
834                                         if (! $tempres)
835                                                 $res = false;
836                                 }
837                         }
838                 }
839                 else {
840                         // Not multi, just a lone insert
841                         if ( $ignore ) {
842                                 pg_query($this->mConn, "SAVEPOINT $ignore");
843                         }
844
845                         $sql .= '(' . $this->makeList( $args ) . ')';
846                         $res = (bool)$this->query( $sql, $fname, $ignore );
847                         if ( $ignore ) {
848                                 $bar = pg_last_error();
849                                 if ($bar != false) {
850                                         pg_query( $this->mConn, "ROLLBACK TO $ignore" );
851                                 }
852                                 else {
853                                         pg_query( $this->mConn, "RELEASE $ignore" );
854                                         $numrowsinserted++;
855                                 }
856                         }
857                 }
858                 if ( $ignore ) {
859                         $olde = error_reporting( $olde );
860                         if ($didbegin) {
861                                 $this->commit();
862                         }
863
864                         // Set the affected row count for the whole operation
865                         $this->mAffectedRows = $numrowsinserted;
866
867                         // IGNORE always returns true
868                         return true;
869                 }
870
871
872                 return $res;
873
874         }
875
876         function tableName( $name ) {
877                 # Replace reserved words with better ones
878                 switch( $name ) {
879                         case 'user':
880                                 return 'mwuser';
881                         case 'text':
882                                 return 'pagecontent';
883                         default:
884                                 return $name;
885                 }
886         }
887
888         /**
889          * Return the next in a sequence, save the value for retrieval via insertId()
890          */
891         function nextSequenceValue( $seqName ) {
892                 $safeseq = preg_replace( "/'/", "''", $seqName );
893                 $res = $this->query( "SELECT nextval('$safeseq')" );
894                 $row = $this->fetchRow( $res );
895                 $this->mInsertId = $row[0];
896                 $this->freeResult( $res );
897                 return $this->mInsertId;
898         }
899
900         /**
901          * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
902          */
903         function currentSequenceValue( $seqName ) {
904                 $safeseq = preg_replace( "/'/", "''", $seqName );
905                 $res = $this->query( "SELECT currval('$safeseq')" );
906                 $row = $this->fetchRow( $res );
907                 $currval = $row[0];
908                 $this->freeResult( $res );
909                 return $currval;
910         }
911
912         /**
913          * Postgres does not have a "USE INDEX" clause, so return an empty string
914          */
915         function useIndexClause( $index ) {
916                 return '';
917         }
918
919         # REPLACE query wrapper
920         # Postgres simulates this with a DELETE followed by INSERT
921         # $row is the row to insert, an associative array
922         # $uniqueIndexes is an array of indexes. Each element may be either a
923         # field name or an array of field names
924         #
925         # It may be more efficient to leave off unique indexes which are unlikely to collide.
926         # However if you do this, you run the risk of encountering errors which wouldn't have
927         # occurred in MySQL
928         function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) {
929                 $table = $this->tableName( $table );
930
931                 if (count($rows)==0) {
932                         return;
933                 }
934
935                 # Single row case
936                 if ( !is_array( reset( $rows ) ) ) {
937                         $rows = array( $rows );
938                 }
939
940                 foreach( $rows as $row ) {
941                         # Delete rows which collide
942                         if ( $uniqueIndexes ) {
943                                 $sql = "DELETE FROM $table WHERE ";
944                                 $first = true;
945                                 foreach ( $uniqueIndexes as $index ) {
946                                         if ( $first ) {
947                                                 $first = false;
948                                                 $sql .= "(";
949                                         } else {
950                                                 $sql .= ') OR (';
951                                         }
952                                         if ( is_array( $index ) ) {
953                                                 $first2 = true;
954                                                 foreach ( $index as $col ) {
955                                                         if ( $first2 ) {
956                                                                 $first2 = false;
957                                                         } else {
958                                                                 $sql .= ' AND ';
959                                                         }
960                                                         $sql .= $col.'=' . $this->addQuotes( $row[$col] );
961                                                 }
962                                         } else {
963                                                 $sql .= $index.'=' . $this->addQuotes( $row[$index] );
964                                         }
965                                 }
966                                 $sql .= ')';
967                                 $this->query( $sql, $fname );
968                         }
969
970                         # Now insert the row
971                         $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
972                                 $this->makeList( $row, LIST_COMMA ) . ')';
973                         $this->query( $sql, $fname );
974                 }
975         }
976
977         # DELETE where the condition is a join
978         function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) {
979                 if ( !$conds ) {
980                         throw new DBUnexpectedError($this,  'Database::deleteJoin() called with empty $conds' );
981                 }
982
983                 $delTable = $this->tableName( $delTable );
984                 $joinTable = $this->tableName( $joinTable );
985                 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
986                 if ( $conds != '*' ) {
987                         $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
988                 }
989                 $sql .= ')';
990
991                 $this->query( $sql, $fname );
992         }
993
994         # Returns the size of a text field, or -1 for "unlimited"
995         function textFieldSize( $table, $field ) {
996                 $table = $this->tableName( $table );
997                 $sql = "SELECT t.typname as ftype,a.atttypmod as size
998                         FROM pg_class c, pg_attribute a, pg_type t
999                         WHERE relname='$table' AND a.attrelid=c.oid AND
1000                                 a.atttypid=t.oid and a.attname='$field'";
1001                 $res =$this->query($sql);
1002                 $row=$this->fetchObject($res);
1003                 if ($row->ftype=="varchar") {
1004                         $size=$row->size-4;
1005                 } else {
1006                         $size=$row->size;
1007                 }
1008                 $this->freeResult( $res );
1009                 return $size;
1010         }
1011
1012         function lowPriorityOption() {
1013                 return '';
1014         }
1015
1016         function limitResult($sql, $limit, $offset=false) {
1017                 return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
1018         }
1019
1020         /**
1021          * Returns an SQL expression for a simple conditional.
1022          * Uses CASE on Postgres
1023          *
1024          * @param $cond String: SQL expression which will result in a boolean value
1025          * @param $trueVal String: SQL expression to return if true
1026          * @param $falseVal String: SQL expression to return if false
1027          * @return String: SQL fragment
1028          */
1029         function conditional( $cond, $trueVal, $falseVal ) {
1030                 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
1031         }
1032
1033         function wasDeadlock() {
1034                 return $this->lastErrno() == '40P01';
1035         }
1036
1037         function timestamp( $ts=0 ) {
1038                 return wfTimestamp(TS_POSTGRES,$ts);
1039         }
1040
1041         /**
1042          * Return aggregated value function call
1043          */
1044         function aggregateValue ($valuedata,$valuename='value') {
1045                 return $valuedata;
1046         }
1047
1048
1049         function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
1050                 // Ignore errors during error handling to avoid infinite recursion
1051                 $ignore = $this->ignoreErrors( true );
1052                 $this->mErrorCount++;
1053
1054                 if ($ignore || $tempIgnore) {
1055                         wfDebug("SQL ERROR (ignored): $error\n");
1056                         $this->ignoreErrors( $ignore );
1057                 }
1058                 else {
1059                         $message = "A database error has occurred\n" .
1060                                 "Query: $sql\n" .
1061                                 "Function: $fname\n" .
1062                                 "Error: $errno $error\n";
1063                         throw new DBUnexpectedError($this, $message);
1064                 }
1065         }
1066
1067         /**
1068          * @return string wikitext of a link to the server software's web site
1069          */
1070         function getSoftwareLink() {
1071                 return "[http://www.postgresql.org/ PostgreSQL]";
1072         }
1073
1074         /**
1075          * @return string Version information from the database
1076          */
1077         function getServerVersion() {
1078                 $versionInfo = pg_version( $this->mConn );
1079                 if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
1080                         // Old client, abort install
1081                         $this->numeric_version = '7.3 or earlier';
1082                 } elseif ( isset( $versionInfo['server'] ) ) {
1083                         // Normal client
1084                         $this->numeric_version = $versionInfo['server'];
1085                 } else {
1086                         // Bug 16937: broken pgsql extension from PHP<5.3
1087                         $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
1088                 }
1089                 return $this->numeric_version;
1090         }
1091
1092         /**
1093          * Query whether a given relation exists (in the given schema, or the
1094          * default mw one if not given)
1095          */
1096         function relationExists( $table, $types, $schema = false ) {
1097                 global $wgDBmwschema;
1098                 if ( !is_array( $types ) )
1099                         $types = array( $types );
1100                 if ( !$schema )
1101                         $schema = $wgDBmwschema;
1102                 $etable = $this->addQuotes( $table );
1103                 $eschema = $this->addQuotes( $schema );
1104                 $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1105                         . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1106                         . "AND c.relkind IN ('" . implode("','", $types) . "')";
1107                 $res = $this->query( $SQL );
1108                 $count = $res ? $res->numRows() : 0;
1109                 if ($res)
1110                         $this->freeResult( $res );
1111                 return $count ? true : false;
1112         }
1113
1114         /*
1115          * For backward compatibility, this function checks both tables and
1116          * views.
1117          */
1118         function tableExists( $table, $schema = false ) {
1119                 return $this->relationExists( $table, array( 'r', 'v' ), $schema );
1120         }
1121
1122         function sequenceExists( $sequence, $schema = false ) {
1123                 return $this->relationExists( $sequence, 'S', $schema );
1124         }
1125
1126         function triggerExists( $table, $trigger ) {
1127                 global $wgDBmwschema;
1128
1129                 $q = <<<END
1130         SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1131                 WHERE relnamespace=pg_namespace.oid AND relkind='r'
1132                       AND tgrelid=pg_class.oid
1133                       AND nspname=%s AND relname=%s AND tgname=%s
1134 END;
1135                 $res = $this->query(sprintf($q,
1136                                 $this->addQuotes($wgDBmwschema),
1137                                 $this->addQuotes($table),
1138                                 $this->addQuotes($trigger)));
1139                 if (!$res)
1140                         return NULL;
1141                 $rows = $res->numRows();
1142                 $this->freeResult( $res );
1143                 return $rows;
1144         }
1145
1146         function ruleExists( $table, $rule ) {
1147                 global $wgDBmwschema;
1148                 $exists = $this->selectField("pg_rules", "rulename",
1149                                 array(  "rulename" => $rule,
1150                                         "tablename" => $table,
1151                                         "schemaname" => $wgDBmwschema ) );
1152                 return $exists === $rule;
1153         }
1154
1155         function constraintExists( $table, $constraint ) {
1156                 global $wgDBmwschema;
1157                 $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ".
1158                            "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1159                         $this->addQuotes($wgDBmwschema),
1160                         $this->addQuotes($table),
1161                         $this->addQuotes($constraint));
1162                 $res = $this->query($SQL);
1163                 if (!$res)
1164                         return NULL;
1165                 $rows = $res->numRows();
1166                 $this->freeResult($res);
1167                 return $rows;
1168         }
1169
1170         /**
1171          * Query whether a given schema exists. Returns the name of the owner
1172          */
1173         function schemaExists( $schema ) {
1174                 $eschema = preg_replace("/'/", "''", $schema);
1175                 $SQL = "SELECT rolname FROM pg_catalog.pg_namespace n, pg_catalog.pg_roles r "
1176                                 ."WHERE n.nspowner=r.oid AND n.nspname = '$eschema'";
1177                 $res = $this->query( $SQL );
1178                 if ( $res && $res->numRows() ) {
1179                         $row = $res->fetchObject();
1180                         $owner = $row->rolname;
1181                 } else {
1182                         $owner = false;
1183                 }
1184                 if ($res)
1185                         $this->freeResult($res);
1186                 return $owner;
1187         }
1188
1189         /**
1190          * Query whether a given column exists in the mediawiki schema
1191          */
1192         function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) {
1193                 global $wgDBmwschema;
1194                 $etable = preg_replace("/'/", "''", $table);
1195                 $eschema = preg_replace("/'/", "''", $wgDBmwschema);
1196                 $ecol = preg_replace("/'/", "''", $field);
1197                 $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a "
1198                         . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
1199                         . "AND a.attrelid = c.oid AND a.attname = '$ecol'";
1200                 $res = $this->query( $SQL, $fname );
1201                 $count = $res ? $res->numRows() : 0;
1202                 if ($res)
1203                         $this->freeResult( $res );
1204                 return $count;
1205         }
1206
1207         function fieldInfo( $table, $field ) {
1208                 return PostgresField::fromText($this, $table, $field);
1209         }
1210         
1211         /**
1212          * pg_field_type() wrapper
1213          */
1214         function fieldType( $res, $index ) {
1215                 if ( $res instanceof ResultWrapper ) {
1216                         $res = $res->result;
1217                 }
1218                 return pg_field_type( $res, $index );
1219         }
1220
1221         function begin( $fname = 'DatabasePostgres::begin' ) {
1222                 $this->query( 'BEGIN', $fname );
1223                 $this->mTrxLevel = 1;
1224         }
1225         function immediateCommit( $fname = 'DatabasePostgres::immediateCommit' ) {
1226                 return true;
1227         }
1228         function commit( $fname = 'DatabasePostgres::commit' ) {
1229                 $this->query( 'COMMIT', $fname );
1230                 $this->mTrxLevel = 0;
1231         }
1232
1233         /* Not even sure why this is used in the main codebase... */
1234         function limitResultForUpdate( $sql, $num ) {
1235                 return $sql;
1236         }
1237
1238         function setup_database() {
1239                 global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
1240
1241                 // Make sure that we can write to the correct schema
1242                 // If not, Postgres will happily and silently go to the next search_path item
1243                 $ctest = "mediawiki_test_table";
1244                 $safeschema = $this->quote_ident($wgDBmwschema);
1245                 if ($this->tableExists($ctest, $wgDBmwschema)) {
1246                         $this->doQuery("DROP TABLE $safeschema.$ctest");
1247                 }
1248                 $SQL = "CREATE TABLE $safeschema.$ctest(a int)";
1249                 $olde = error_reporting( 0 );
1250                 $res = $this->doQuery($SQL);
1251                 error_reporting( $olde );
1252                 if (!$res) {
1253                         print "<b>FAILED</b>. Make sure that the user \"" . htmlspecialchars( $wgDBuser ) . 
1254                                 "\" can write to the schema \"" . htmlspecialchars( $wgDBmwschema ) . "\"</li>\n";
1255                         dieout("</ul>");
1256                 }
1257                 $this->doQuery("DROP TABLE $safeschema.$ctest");
1258
1259                 $res = dbsource( "../maintenance/postgres/tables.sql", $this);
1260
1261                 ## Update version information
1262                 $mwv = $this->addQuotes($wgVersion);
1263                 $pgv = $this->addQuotes($this->getServerVersion());
1264                 $pgu = $this->addQuotes($this->mUser);
1265                 $mws = $this->addQuotes($wgDBmwschema);
1266                 $tss = $this->addQuotes($wgDBts2schema);
1267                 $pgp = $this->addQuotes($wgDBport);
1268                 $dbn = $this->addQuotes($this->mDBname);
1269                 $ctype = $this->addQuotes( pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0) );
1270
1271                 $SQL = "UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, ".
1272                                 "mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, ".
1273                                 "ctype = $ctype ".
1274                                 "WHERE type = 'Creation'";
1275                 $this->query($SQL);
1276
1277                 ## Avoid the non-standard "REPLACE INTO" syntax
1278                 $f = fopen( "../maintenance/interwiki.sql", 'r' );
1279                 if ($f == false ) {
1280                         dieout( "<li>Could not find the interwiki.sql file");
1281                 }
1282                 ## We simply assume it is already empty as we have just created it
1283                 $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
1284                 while ( ! feof( $f ) ) {
1285                         $line = fgets($f,1024);
1286                         $matches = array();
1287                         if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) {
1288                                 continue;
1289                         }
1290                         $this->query("$SQL $matches[1],$matches[2])");
1291                 }
1292                 print " (table interwiki successfully populated)...\n";
1293
1294                 $this->doQuery("COMMIT");
1295         }
1296
1297         function encodeBlob( $b ) {
1298                 return new Blob ( pg_escape_bytea( $b ) ) ;
1299         }
1300
1301         function decodeBlob( $b ) {
1302                 if ($b instanceof Blob) {
1303                         $b = $b->fetch();
1304                 }
1305                 return pg_unescape_bytea( $b );
1306         }
1307
1308         function strencode( $s ) { ## Should not be called by us
1309                 return pg_escape_string( $s );
1310         }
1311
1312         function addQuotes( $s ) {
1313                 if ( is_null( $s ) ) {
1314                         return 'NULL';
1315                 } else if ( is_bool( $s ) ) {
1316                         return intval( $s );
1317                 } else if ($s instanceof Blob) {
1318                         return "'".$s->fetch($s)."'";
1319                 }
1320                 return "'" . pg_escape_string($s) . "'";
1321         }
1322
1323         function quote_ident( $s ) {
1324                 return '"' . preg_replace( '/"/', '""', $s) . '"';
1325         }
1326
1327         /* For now, does nothing */
1328         function selectDB( $db ) {
1329                 return true;
1330         }
1331
1332         /**
1333          * Postgres specific version of replaceVars.
1334          * Calls the parent version in Database.php
1335          *
1336          * @private
1337          *
1338          * @param $ins String: SQL string, read from a stream (usually tables.sql)
1339          *
1340          * @return string SQL string
1341          */
1342         protected function replaceVars( $ins ) {
1343
1344                 $ins = parent::replaceVars( $ins );
1345
1346                 if ($this->numeric_version >= 8.3) {
1347                         // Thanks for not providing backwards-compatibility, 8.3
1348                         $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
1349                 }
1350
1351                 if ($this->numeric_version <= 8.1) { // Our minimum version
1352                         $ins = str_replace( 'USING gin', 'USING gist', $ins );
1353                 }
1354
1355                 return $ins;
1356         }
1357
1358         /**
1359          * Various select options
1360          *
1361          * @private
1362          *
1363          * @param $options Array: an associative array of options to be turned into
1364          *              an SQL query, valid keys are listed in the function.
1365          * @return array
1366          */
1367         function makeSelectOptions( $options ) {
1368                 $preLimitTail = $postLimitTail = '';
1369                 $startOpts = $useIndex = '';
1370
1371                 $noKeyOptions = array();
1372                 foreach ( $options as $key => $option ) {
1373                         if ( is_numeric( $key ) ) {
1374                                 $noKeyOptions[$option] = true;
1375                         }
1376                 }
1377
1378                 if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY'];
1379                 if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
1380                 if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY'];
1381
1382                 //if (isset($options['LIMIT'])) {
1383                 //      $tailOpts .= $this->limitResult('', $options['LIMIT'],
1384                 //              isset($options['OFFSET']) ? $options['OFFSET']
1385                 //              : false);
1386                 //}
1387
1388                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
1389                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
1390                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
1391
1392                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
1393         }
1394
1395         public function setTimeout( $timeout ) {
1396                 // @todo fixme no-op
1397         }
1398
1399         function ping() {
1400                 wfDebug( "Function ping() not written for DatabasePostgres.php yet");
1401                 return true;
1402         }
1403
1404         /**
1405          * How lagged is this slave?
1406          *
1407          */
1408         public function getLag() {
1409                 # Not implemented for PostgreSQL
1410                 return false;
1411         }
1412
1413         function setFakeSlaveLag( $lag ) {}
1414         function setFakeMaster( $enabled = true ) {}
1415
1416         function getDBname() {
1417                 return $this->mDBname;
1418         }
1419
1420         function getServer() {
1421                 return $this->mServer;
1422         }
1423
1424         function buildConcat( $stringList ) {
1425                 return implode( ' || ', $stringList );
1426         }
1427
1428         /* These are not used yet, but we know we don't want the default version */
1429
1430         public function lock( $lockName, $method ) {
1431                 return true;
1432         }
1433         public function unlock( $lockName, $method ) {
1434                 return true;
1435         }
1436         
1437         public function getSearchEngine() {
1438                 return "SearchPostgres";
1439         }
1440
1441 } // end DatabasePostgres class