MediaWiki  REL1_19
DatabasePostgres.php
Go to the documentation of this file.
00001 <?php
00009 class PostgresField implements Field {
00010         private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname;
00011 
00018         static function fromText( $db, $table, $field ) {
00019                 global $wgDBmwschema;
00020 
00021                 $q = <<<SQL
00022 SELECT
00023  attnotnull, attlen, COALESCE(conname, '') AS conname,
00024  COALESCE(condeferred, 'f') AS deferred,
00025  COALESCE(condeferrable, 'f') AS deferrable,
00026  CASE WHEN typname = 'int2' THEN 'smallint'
00027   WHEN typname = 'int4' THEN 'integer'
00028   WHEN typname = 'int8' THEN 'bigint'
00029   WHEN typname = 'bpchar' THEN 'char'
00030  ELSE typname END AS typname
00031 FROM pg_class c
00032 JOIN pg_namespace n ON (n.oid = c.relnamespace)
00033 JOIN pg_attribute a ON (a.attrelid = c.oid)
00034 JOIN pg_type t ON (t.oid = a.atttypid)
00035 LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
00036 WHERE relkind = 'r'
00037 AND nspname=%s
00038 AND relname=%s
00039 AND attname=%s;
00040 SQL;
00041 
00042                 $table = $db->tableName( $table, 'raw' );
00043                 $res = $db->query(
00044                         sprintf( $q,
00045                                 $db->addQuotes( $wgDBmwschema ),
00046                                 $db->addQuotes( $table ),
00047                                 $db->addQuotes( $field )
00048                         )
00049                 );
00050                 $row = $db->fetchObject( $res );
00051                 if ( !$row ) {
00052                         return null;
00053                 }
00054                 $n = new PostgresField;
00055                 $n->type = $row->typname;
00056                 $n->nullable = ( $row->attnotnull == 'f' );
00057                 $n->name = $field;
00058                 $n->tablename = $table;
00059                 $n->max_length = $row->attlen;
00060                 $n->deferrable = ( $row->deferrable == 't' );
00061                 $n->deferred = ( $row->deferred == 't' );
00062                 $n->conname = $row->conname;
00063                 return $n;
00064         }
00065 
00066         function name() {
00067                 return $this->name;
00068         }
00069 
00070         function tableName() {
00071                 return $this->tablename;
00072         }
00073 
00074         function type() {
00075                 return $this->type;
00076         }
00077 
00078         function isNullable() {
00079                 return $this->nullable;
00080         }
00081 
00082         function maxLength() {
00083                 return $this->max_length;
00084         }
00085 
00086         function is_deferrable() {
00087                 return $this->deferrable;
00088         }
00089 
00090         function is_deferred() {
00091                 return $this->deferred;
00092         }
00093 
00094         function conname() {
00095                 return $this->conname;
00096         }
00097 
00098 }
00099 
00103 class DatabasePostgres extends DatabaseBase {
00104         var $mInsertId = null;
00105         var $mLastResult = null;
00106         var $numeric_version = null;
00107         var $mAffectedRows = null;
00108 
00109         function getType() {
00110                 return 'postgres';
00111         }
00112 
00113         function cascadingDeletes() {
00114                 return true;
00115         }
00116         function cleanupTriggers() {
00117                 return true;
00118         }
00119         function strictIPs() {
00120                 return true;
00121         }
00122         function realTimestamps() {
00123                 return true;
00124         }
00125         function implicitGroupby() {
00126                 return false;
00127         }
00128         function implicitOrderby() {
00129                 return false;
00130         }
00131         function searchableIPs() {
00132                 return true;
00133         }
00134         function functionalIndexes() {
00135                 return true;
00136         }
00137 
00138         function hasConstraint( $name ) {
00139                 global $wgDBmwschema;
00140                 $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
00141                                 pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $wgDBmwschema ) ."'";
00142                 $res = $this->doQuery( $SQL );
00143                 return $this->numRows( $res );
00144         }
00145 
00149         function open( $server, $user, $password, $dbName ) {
00150                 # Test for Postgres support, to avoid suppressed fatal error
00151                 if ( !function_exists( 'pg_connect' ) ) {
00152                         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" );
00153                 }
00154 
00155                 global $wgDBport;
00156 
00157                 if ( !strlen( $user ) ) { # e.g. the class is being loaded
00158                         return;
00159                 }
00160 
00161                 $this->close();
00162                 $this->mServer = $server;
00163                 $port = $wgDBport;
00164                 $this->mUser = $user;
00165                 $this->mPassword = $password;
00166                 $this->mDBname = $dbName;
00167 
00168                 $connectVars = array(
00169                         'dbname' => $dbName,
00170                         'user' => $user,
00171                         'password' => $password
00172                 );
00173                 if ( $server != false && $server != '' ) {
00174                         $connectVars['host'] = $server;
00175                 }
00176                 if ( $port != false && $port != '' ) {
00177                         $connectVars['port'] = $port;
00178                 }
00179                 $connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
00180 
00181                 $this->installErrorHandler();
00182                 $this->mConn = pg_connect( $connectString );
00183                 $phpError = $this->restoreErrorHandler();
00184 
00185                 if ( !$this->mConn ) {
00186                         wfDebug( "DB connection error\n" );
00187                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
00188                         wfDebug( $this->lastError() . "\n" );
00189                         throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
00190                 }
00191 
00192                 $this->mOpened = true;
00193 
00194                 global $wgCommandLineMode;
00195                 # If called from the command-line (e.g. importDump), only show errors
00196                 if ( $wgCommandLineMode ) {
00197                         $this->doQuery( "SET client_min_messages = 'ERROR'" );
00198                 }
00199 
00200                 $this->query( "SET client_encoding='UTF8'", __METHOD__ );
00201                 $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
00202                 $this->query( "SET timezone = 'GMT'", __METHOD__ );
00203                 $this->query( "SET standard_conforming_strings = on", __METHOD__ );
00204 
00205                 global $wgDBmwschema;
00206                 if ( $this->schemaExists( $wgDBmwschema ) ) {
00207                         $safeschema = $this->addIdentifierQuotes( $wgDBmwschema );
00208                         $this->doQuery( "SET search_path = $safeschema" );
00209                 } else {
00210                         $this->doQuery( "SET search_path = public" );
00211                 }
00212 
00213                 return $this->mConn;
00214         }
00215 
00221         function selectDB( $db ) {
00222                 if ( $this->mDBname !== $db ) {
00223                         return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
00224                 } else {
00225                         return true;
00226                 }
00227         }
00228 
00229         function makeConnectionString( $vars ) {
00230                 $s = '';
00231                 foreach ( $vars as $name => $value ) {
00232                         $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
00233                 }
00234                 return $s;
00235         }
00236 
00241         function close() {
00242                 $this->mOpened = false;
00243                 if ( $this->mConn ) {
00244                         return pg_close( $this->mConn );
00245                 } else {
00246                         return true;
00247                 }
00248         }
00249 
00250         protected function doQuery( $sql ) {
00251                 if ( function_exists( 'mb_convert_encoding' ) ) {
00252                         $sql = mb_convert_encoding( $sql, 'UTF-8' );
00253                 }
00254                 $this->mLastResult = pg_query( $this->mConn, $sql );
00255                 $this->mAffectedRows = null; // use pg_affected_rows(mLastResult)
00256                 return $this->mLastResult;
00257         }
00258 
00259         function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
00260                 return $this->query( $sql, $fname, true );
00261         }
00262 
00263         function freeResult( $res ) {
00264                 if ( $res instanceof ResultWrapper ) {
00265                         $res = $res->result;
00266                 }
00267                 wfSuppressWarnings();
00268                 $ok = pg_free_result( $res );
00269                 wfRestoreWarnings();
00270                 if ( !$ok ) {
00271                         throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
00272                 }
00273         }
00274 
00275         function fetchObject( $res ) {
00276                 if ( $res instanceof ResultWrapper ) {
00277                         $res = $res->result;
00278                 }
00279                 wfSuppressWarnings();
00280                 $row = pg_fetch_object( $res );
00281                 wfRestoreWarnings();
00282                 # @todo FIXME: HACK HACK HACK HACK debug
00283 
00284                 # @todo hashar: not sure if the following test really trigger if the object
00285                 #          fetching failed.
00286                 if( pg_last_error( $this->mConn ) ) {
00287                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00288                 }
00289                 return $row;
00290         }
00291 
00292         function fetchRow( $res ) {
00293                 if ( $res instanceof ResultWrapper ) {
00294                         $res = $res->result;
00295                 }
00296                 wfSuppressWarnings();
00297                 $row = pg_fetch_array( $res );
00298                 wfRestoreWarnings();
00299                 if( pg_last_error( $this->mConn ) ) {
00300                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00301                 }
00302                 return $row;
00303         }
00304 
00305         function numRows( $res ) {
00306                 if ( $res instanceof ResultWrapper ) {
00307                         $res = $res->result;
00308                 }
00309                 wfSuppressWarnings();
00310                 $n = pg_num_rows( $res );
00311                 wfRestoreWarnings();
00312                 if( pg_last_error( $this->mConn ) ) {
00313                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00314                 }
00315                 return $n;
00316         }
00317 
00318         function numFields( $res ) {
00319                 if ( $res instanceof ResultWrapper ) {
00320                         $res = $res->result;
00321                 }
00322                 return pg_num_fields( $res );
00323         }
00324 
00325         function fieldName( $res, $n ) {
00326                 if ( $res instanceof ResultWrapper ) {
00327                         $res = $res->result;
00328                 }
00329                 return pg_field_name( $res, $n );
00330         }
00331 
00335         function insertId() {
00336                 return $this->mInsertId;
00337         }
00338 
00339         function dataSeek( $res, $row ) {
00340                 if ( $res instanceof ResultWrapper ) {
00341                         $res = $res->result;
00342                 }
00343                 return pg_result_seek( $res, $row );
00344         }
00345 
00346         function lastError() {
00347                 if ( $this->mConn ) {
00348                         return pg_last_error();
00349                 } else {
00350                         return 'No database connection';
00351                 }
00352         }
00353         function lastErrno() {
00354                 return pg_last_error() ? 1 : 0;
00355         }
00356 
00357         function affectedRows() {
00358                 if ( !is_null( $this->mAffectedRows ) ) {
00359                         // Forced result for simulated queries
00360                         return $this->mAffectedRows;
00361                 }
00362                 if( empty( $this->mLastResult ) ) {
00363                         return 0;
00364                 }
00365                 return pg_affected_rows( $this->mLastResult );
00366         }
00367 
00375         function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
00376                 $options['EXPLAIN'] = true;
00377                 $res = $this->select( $table, $vars, $conds, $fname, $options );
00378                 $rows = -1;
00379                 if ( $res ) {
00380                         $row = $this->fetchRow( $res );
00381                         $count = array();
00382                         if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
00383                                 $rows = $count[1];
00384                         }
00385                 }
00386                 return $rows;
00387         }
00388 
00393         function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) {
00394                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
00395                 $res = $this->query( $sql, $fname );
00396                 if ( !$res ) {
00397                         return null;
00398                 }
00399                 foreach ( $res as $row ) {
00400                         if ( $row->indexname == $this->indexName( $index ) ) {
00401                                 return $row;
00402                         }
00403                 }
00404                 return false;
00405         }
00406 
00407         function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
00408                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
00409                         " AND indexdef LIKE 'CREATE UNIQUE%(" .
00410                         $this->strencode( $this->indexName( $index ) ) .
00411                         ")'";
00412                 $res = $this->query( $sql, $fname );
00413                 if ( !$res ) {
00414                         return null;
00415                 }
00416                 foreach ( $res as $row ) {
00417                         return true;
00418                 }
00419                 return false;
00420         }
00421 
00435         function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
00436                 if ( !count( $args ) ) {
00437                         return true;
00438                 }
00439 
00440                 $table = $this->tableName( $table );
00441                 if (! isset( $this->numeric_version ) ) {
00442                         $this->getServerVersion();
00443                 }
00444 
00445                 if ( !is_array( $options ) ) {
00446                         $options = array( $options );
00447                 }
00448 
00449                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
00450                         $multi = true;
00451                         $keys = array_keys( $args[0] );
00452                 } else {
00453                         $multi = false;
00454                         $keys = array_keys( $args );
00455                 }
00456 
00457                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
00458                 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
00459 
00460                 // If we are not in a transaction, we need to be for savepoint trickery
00461                 $didbegin = 0;
00462                 if ( $ignore ) {
00463                         if ( !$this->mTrxLevel ) {
00464                                 $this->begin();
00465                                 $didbegin = 1;
00466                         }
00467                         $olde = error_reporting( 0 );
00468                         // For future use, we may want to track the number of actual inserts
00469                         // Right now, insert (all writes) simply return true/false
00470                         $numrowsinserted = 0;
00471                 }
00472 
00473                 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
00474 
00475                 if ( $multi ) {
00476                         if ( $this->numeric_version >= 8.2 && !$ignore ) {
00477                                 $first = true;
00478                                 foreach ( $args as $row ) {
00479                                         if ( $first ) {
00480                                                 $first = false;
00481                                         } else {
00482                                                 $sql .= ',';
00483                                         }
00484                                         $sql .= '(' . $this->makeList( $row ) . ')';
00485                                 }
00486                                 $res = (bool)$this->query( $sql, $fname, $ignore );
00487                         } else {
00488                                 $res = true;
00489                                 $origsql = $sql;
00490                                 foreach ( $args as $row ) {
00491                                         $tempsql = $origsql;
00492                                         $tempsql .= '(' . $this->makeList( $row ) . ')';
00493 
00494                                         if ( $ignore ) {
00495                                                 pg_query( $this->mConn, "SAVEPOINT $ignore" );
00496                                         }
00497 
00498                                         $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
00499 
00500                                         if ( $ignore ) {
00501                                                 $bar = pg_last_error();
00502                                                 if ( $bar != false ) {
00503                                                         pg_query( $this->mConn, "ROLLBACK TO $ignore" );
00504                                                 } else {
00505                                                         pg_query( $this->mConn, "RELEASE $ignore" );
00506                                                         $numrowsinserted++;
00507                                                 }
00508                                         }
00509 
00510                                         // If any of them fail, we fail overall for this function call
00511                                         // Note that this will be ignored if IGNORE is set
00512                                         if ( !$tempres ) {
00513                                                 $res = false;
00514                                         }
00515                                 }
00516                         }
00517                 } else {
00518                         // Not multi, just a lone insert
00519                         if ( $ignore ) {
00520                                 pg_query($this->mConn, "SAVEPOINT $ignore");
00521                         }
00522 
00523                         $sql .= '(' . $this->makeList( $args ) . ')';
00524                         $res = (bool)$this->query( $sql, $fname, $ignore );
00525                         if ( $ignore ) {
00526                                 $bar = pg_last_error();
00527                                 if ( $bar != false ) {
00528                                         pg_query( $this->mConn, "ROLLBACK TO $ignore" );
00529                                 } else {
00530                                         pg_query( $this->mConn, "RELEASE $ignore" );
00531                                         $numrowsinserted++;
00532                                 }
00533                         }
00534                 }
00535                 if ( $ignore ) {
00536                         $olde = error_reporting( $olde );
00537                         if ( $didbegin ) {
00538                                 $this->commit();
00539                         }
00540 
00541                         // Set the affected row count for the whole operation
00542                         $this->mAffectedRows = $numrowsinserted;
00543 
00544                         // IGNORE always returns true
00545                         return true;
00546                 }
00547 
00548                 return $res;
00549         }
00550 
00559         function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
00560                 $insertOptions = array(), $selectOptions = array() )
00561         {
00562                 $destTable = $this->tableName( $destTable );
00563 
00564                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
00565                 $ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : '';
00566 
00567                 if( is_array( $insertOptions ) ) {
00568                         $insertOptions = implode( ' ', $insertOptions ); // FIXME: This is unused
00569                 }
00570                 if( !is_array( $selectOptions ) ) {
00571                         $selectOptions = array( $selectOptions );
00572                 }
00573                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
00574                 if( is_array( $srcTable ) ) {
00575                         $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
00576                 } else {
00577                         $srcTable = $this->tableName( $srcTable );
00578                 }
00579 
00580                 // If we are not in a transaction, we need to be for savepoint trickery
00581                 $didbegin = 0;
00582                 if ( $ignore ) {
00583                         if( !$this->mTrxLevel ) {
00584                                 $this->begin();
00585                                 $didbegin = 1;
00586                         }
00587                         $olde = error_reporting( 0 );
00588                         $numrowsinserted = 0;
00589                         pg_query( $this->mConn, "SAVEPOINT $ignore");
00590                 }
00591 
00592                 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
00593                                 " SELECT $startOpts " . implode( ',', $varMap ) .
00594                                 " FROM $srcTable $useIndex";
00595 
00596                 if ( $conds != '*' ) {
00597                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
00598                 }
00599 
00600                 $sql .= " $tailOpts";
00601 
00602                 $res = (bool)$this->query( $sql, $fname, $ignore );
00603                 if( $ignore ) {
00604                         $bar = pg_last_error();
00605                         if( $bar != false ) {
00606                                 pg_query( $this->mConn, "ROLLBACK TO $ignore" );
00607                         } else {
00608                                 pg_query( $this->mConn, "RELEASE $ignore" );
00609                                 $numrowsinserted++;
00610                         }
00611                         $olde = error_reporting( $olde );
00612                         if( $didbegin ) {
00613                                 $this->commit();
00614                         }
00615 
00616                         // Set the affected row count for the whole operation
00617                         $this->mAffectedRows = $numrowsinserted;
00618 
00619                         // IGNORE always returns true
00620                         return true;
00621                 }
00622 
00623                 return $res;
00624         }
00625 
00626         function tableName( $name, $format = 'quoted' ) {
00627                 # Replace reserved words with better ones
00628                 switch( $name ) {
00629                         case 'user':
00630                                 return $this->realTableName( 'mwuser', $format );
00631                         case 'text':
00632                                 return $this->realTableName( 'pagecontent', $format );
00633                         default:
00634                                 return $this->realTableName( $name, $format );
00635                 }
00636         }
00637 
00638         /* Don't cheat on installer */
00639         function realTableName( $name, $format = 'quoted' ) {
00640                 return parent::tableName( $name, $format );
00641         }
00642 
00646         function nextSequenceValue( $seqName ) {
00647                 $safeseq = str_replace( "'", "''", $seqName );
00648                 $res = $this->query( "SELECT nextval('$safeseq')" );
00649                 $row = $this->fetchRow( $res );
00650                 $this->mInsertId = $row[0];
00651                 return $this->mInsertId;
00652         }
00653 
00657         function currentSequenceValue( $seqName ) {
00658                 $safeseq = str_replace( "'", "''", $seqName );
00659                 $res = $this->query( "SELECT currval('$safeseq')" );
00660                 $row = $this->fetchRow( $res );
00661                 $currval = $row[0];
00662                 return $currval;
00663         }
00664 
00665         # Returns the size of a text field, or -1 for "unlimited"
00666         function textFieldSize( $table, $field ) {
00667                 $table = $this->tableName( $table );
00668                 $sql = "SELECT t.typname as ftype,a.atttypmod as size
00669                         FROM pg_class c, pg_attribute a, pg_type t
00670                         WHERE relname='$table' AND a.attrelid=c.oid AND
00671                                 a.atttypid=t.oid and a.attname='$field'";
00672                 $res =$this->query( $sql );
00673                 $row = $this->fetchObject( $res );
00674                 if ( $row->ftype == 'varchar' ) {
00675                         $size = $row->size - 4;
00676                 } else {
00677                         $size = $row->size;
00678                 }
00679                 return $size;
00680         }
00681 
00682         function limitResult( $sql, $limit, $offset = false ) {
00683                 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
00684         }
00685 
00686         function wasDeadlock() {
00687                 return $this->lastErrno() == '40P01';
00688         }
00689 
00690         function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) {
00691                 $newName = $this->addIdentifierQuotes( $newName );
00692                 $oldName = $this->addIdentifierQuotes( $oldName );
00693                 return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
00694         }
00695 
00696         function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) {
00697                 global $wgDBmwschema;
00698                 $eschema = $this->addQuotes( $wgDBmwschema );
00699                 $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
00700 
00701                 $endArray = array();
00702 
00703                 foreach( $result as $table ) {
00704                         $vars = get_object_vars($table);
00705                         $table = array_pop( $vars );
00706                         if( !$prefix || strpos( $table, $prefix ) === 0 ) {
00707                                 $endArray[] = $table;
00708                         }
00709                 }
00710 
00711                 return $endArray;
00712         }
00713 
00714         function timestamp( $ts = 0 ) {
00715                 return wfTimestamp( TS_POSTGRES, $ts );
00716         }
00717 
00721         function aggregateValue( $valuedata, $valuename = 'value' ) {
00722                 return $valuedata;
00723         }
00724 
00728         public static function getSoftwareLink() {
00729                 return '[http://www.postgresql.org/ PostgreSQL]';
00730         }
00731 
00735         function getServerVersion() {
00736                 if ( !isset( $this->numeric_version ) ) {
00737                         $versionInfo = pg_version( $this->mConn );
00738                         if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
00739                                 // Old client, abort install
00740                                 $this->numeric_version = '7.3 or earlier';
00741                         } elseif ( isset( $versionInfo['server'] ) ) {
00742                                 // Normal client
00743                                 $this->numeric_version = $versionInfo['server'];
00744                         } else {
00745                                 // Bug 16937: broken pgsql extension from PHP<5.3
00746                                 $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
00747                         }
00748                 }
00749                 return $this->numeric_version;
00750         }
00751 
00756         function relationExists( $table, $types, $schema = false ) {
00757                 global $wgDBmwschema;
00758                 if ( !is_array( $types ) ) {
00759                         $types = array( $types );
00760                 }
00761                 if ( !$schema ) {
00762                         $schema = $wgDBmwschema;
00763                 }
00764                 $table = $this->realTableName( $table, 'raw' );
00765                 $etable = $this->addQuotes( $table );
00766                 $eschema = $this->addQuotes( $schema );
00767                 $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
00768                         . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
00769                         . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
00770                 $res = $this->query( $SQL );
00771                 $count = $res ? $res->numRows() : 0;
00772                 return (bool)$count;
00773         }
00774 
00779         function tableExists( $table, $fname = __METHOD__, $schema = false ) {
00780                 return $this->relationExists( $table, array( 'r', 'v' ), $schema );
00781         }
00782 
00783         function sequenceExists( $sequence, $schema = false ) {
00784                 return $this->relationExists( $sequence, 'S', $schema );
00785         }
00786 
00787         function triggerExists( $table, $trigger ) {
00788                 global $wgDBmwschema;
00789 
00790                 $q = <<<SQL
00791         SELECT 1 FROM pg_class, pg_namespace, pg_trigger
00792                 WHERE relnamespace=pg_namespace.oid AND relkind='r'
00793                           AND tgrelid=pg_class.oid
00794                           AND nspname=%s AND relname=%s AND tgname=%s
00795 SQL;
00796                 $res = $this->query(
00797                         sprintf(
00798                                 $q,
00799                                 $this->addQuotes( $wgDBmwschema ),
00800                                 $this->addQuotes( $table ),
00801                                 $this->addQuotes( $trigger )
00802                         )
00803                 );
00804                 if ( !$res ) {
00805                         return null;
00806                 }
00807                 $rows = $res->numRows();
00808                 return $rows;
00809         }
00810 
00811         function ruleExists( $table, $rule ) {
00812                 global $wgDBmwschema;
00813                 $exists = $this->selectField( 'pg_rules', 'rulename',
00814                         array(
00815                                 'rulename' => $rule,
00816                                 'tablename' => $table,
00817                                 'schemaname' => $wgDBmwschema
00818                         )
00819                 );
00820                 return $exists === $rule;
00821         }
00822 
00823         function constraintExists( $table, $constraint ) {
00824                 global $wgDBmwschema;
00825                 $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
00826                            "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
00827                         $this->addQuotes( $wgDBmwschema ),
00828                         $this->addQuotes( $table ),
00829                         $this->addQuotes( $constraint )
00830                 );
00831                 $res = $this->query( $SQL );
00832                 if ( !$res ) {
00833                         return null;
00834                 }
00835                 $rows = $res->numRows();
00836                 return $rows;
00837         }
00838 
00842         function schemaExists( $schema ) {
00843                 $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
00844                         array( 'nspname' => $schema ), __METHOD__ );
00845                 return (bool)$exists;
00846         }
00847 
00851         function roleExists( $roleName ) {
00852                 $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
00853                         array( 'rolname' => $roleName ), __METHOD__ );
00854                 return (bool)$exists;
00855         }
00856 
00857         function fieldInfo( $table, $field ) {
00858                 return PostgresField::fromText( $this, $table, $field );
00859         }
00860 
00864         function fieldType( $res, $index ) {
00865                 if ( $res instanceof ResultWrapper ) {
00866                         $res = $res->result;
00867                 }
00868                 return pg_field_type( $res, $index );
00869         }
00870 
00871         /* Not even sure why this is used in the main codebase... */
00872         function limitResultForUpdate( $sql, $num ) {
00873                 return $sql;
00874         }
00875 
00880         function encodeBlob( $b ) {
00881                 return new Blob( pg_escape_bytea( $this->mConn, $b ) );
00882         }
00883 
00884         function decodeBlob( $b ) {
00885                 if ( $b instanceof Blob ) {
00886                         $b = $b->fetch();
00887                 }
00888                 return pg_unescape_bytea( $b );
00889         }
00890 
00891         function strencode( $s ) { # Should not be called by us
00892                 return pg_escape_string( $this->mConn, $s );
00893         }
00894 
00899         function addQuotes( $s ) {
00900                 if ( is_null( $s ) ) {
00901                         return 'NULL';
00902                 } elseif ( is_bool( $s ) ) {
00903                         return intval( $s );
00904                 } elseif ( $s instanceof Blob ) {
00905                         return "'" . $s->fetch( $s ) . "'";
00906                 }
00907                 return "'" . pg_escape_string( $this->mConn, $s ) . "'";
00908         }
00909 
00920         protected function replaceVars( $ins ) {
00921                 $ins = parent::replaceVars( $ins );
00922 
00923                 if ( $this->numeric_version >= 8.3 ) {
00924                         // Thanks for not providing backwards-compatibility, 8.3
00925                         $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
00926                 }
00927 
00928                 if ( $this->numeric_version <= 8.1 ) { // Our minimum version
00929                         $ins = str_replace( 'USING gin', 'USING gist', $ins );
00930                 }
00931 
00932                 return $ins;
00933         }
00934 
00944         function makeSelectOptions( $options ) {
00945                 $preLimitTail = $postLimitTail = '';
00946                 $startOpts = $useIndex = '';
00947 
00948                 $noKeyOptions = array();
00949                 foreach ( $options as $key => $option ) {
00950                         if ( is_numeric( $key ) ) {
00951                                 $noKeyOptions[$option] = true;
00952                         }
00953                 }
00954 
00955                 if ( isset( $options['GROUP BY'] ) ) {
00956                         $gb = is_array( $options['GROUP BY'] )
00957                                 ? implode( ',', $options['GROUP BY'] )
00958                                 : $options['GROUP BY'];
00959                         $preLimitTail .= " GROUP BY {$gb}";
00960                 }
00961 
00962                 if ( isset( $options['HAVING'] ) ) {
00963                         $preLimitTail .= " HAVING {$options['HAVING']}";
00964                 }
00965 
00966                 if ( isset( $options['ORDER BY'] ) ) {
00967                         $ob = is_array( $options['ORDER BY'] )
00968                                 ? implode( ',', $options['ORDER BY'] )
00969                                 : $options['ORDER BY'];
00970                         $preLimitTail .= " ORDER BY {$ob}";
00971                 }
00972 
00973                 //if ( isset( $options['LIMIT'] ) ) {
00974                 //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
00975                 //              isset( $options['OFFSET'] ) ? $options['OFFSET']
00976                 //              : false );
00977                 //}
00978 
00979                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
00980                         $postLimitTail .= ' FOR UPDATE';
00981                 }
00982                 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
00983                         $postLimitTail .= ' LOCK IN SHARE MODE';
00984                 }
00985                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
00986                         $startOpts .= 'DISTINCT';
00987                 }
00988 
00989                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
00990         }
00991 
00992         function setFakeMaster( $enabled = true ) {}
00993 
00994         function getDBname() {
00995                 return $this->mDBname;
00996         }
00997 
00998         function getServer() {
00999                 return $this->mServer;
01000         }
01001 
01002         function buildConcat( $stringList ) {
01003                 return implode( ' || ', $stringList );
01004         }
01005 
01006         public function getSearchEngine() {
01007                 return 'SearchPostgres';
01008         }
01009 
01010         public function streamStatementEnd( &$sql, &$newLine ) {
01011                 # Allow dollar quoting for function declarations
01012                 if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
01013                         if ( $this->delimiter ) {
01014                                 $this->delimiter = false;
01015                         }
01016                         else {
01017                                 $this->delimiter = ';';
01018                         }
01019                 }
01020                 return parent::streamStatementEnd( $sql, $newLine );
01021         }
01022 } // end DatabasePostgres class