MediaWiki  REL1_20
DatabasePostgres.php
Go to the documentation of this file.
00001 <?php
00024 class PostgresField implements Field {
00025         private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
00026                 $has_default, $default;
00027 
00034         static function fromText( $db, $table, $field ) {
00035                 $q = <<<SQL
00036 SELECT
00037  attnotnull, attlen, conname AS conname,
00038  atthasdef,
00039  adsrc,
00040  COALESCE(condeferred, 'f') AS deferred,
00041  COALESCE(condeferrable, 'f') AS deferrable,
00042  CASE WHEN typname = 'int2' THEN 'smallint'
00043   WHEN typname = 'int4' THEN 'integer'
00044   WHEN typname = 'int8' THEN 'bigint'
00045   WHEN typname = 'bpchar' THEN 'char'
00046  ELSE typname END AS typname
00047 FROM pg_class c
00048 JOIN pg_namespace n ON (n.oid = c.relnamespace)
00049 JOIN pg_attribute a ON (a.attrelid = c.oid)
00050 JOIN pg_type t ON (t.oid = a.atttypid)
00051 LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
00052 LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
00053 WHERE relkind = 'r'
00054 AND nspname=%s
00055 AND relname=%s
00056 AND attname=%s;
00057 SQL;
00058 
00059                 $table = $db->tableName( $table, 'raw' );
00060                 $res = $db->query(
00061                         sprintf( $q,
00062                                 $db->addQuotes( $db->getCoreSchema() ),
00063                                 $db->addQuotes( $table ),
00064                                 $db->addQuotes( $field )
00065                         )
00066                 );
00067                 $row = $db->fetchObject( $res );
00068                 if ( !$row ) {
00069                         return null;
00070                 }
00071                 $n = new PostgresField;
00072                 $n->type = $row->typname;
00073                 $n->nullable = ( $row->attnotnull == 'f' );
00074                 $n->name = $field;
00075                 $n->tablename = $table;
00076                 $n->max_length = $row->attlen;
00077                 $n->deferrable = ( $row->deferrable == 't' );
00078                 $n->deferred = ( $row->deferred == 't' );
00079                 $n->conname = $row->conname;
00080                 $n->has_default = ( $row->atthasdef === 't' );
00081                 $n->default = $row->adsrc;
00082                 return $n;
00083         }
00084 
00085         function name() {
00086                 return $this->name;
00087         }
00088 
00089         function tableName() {
00090                 return $this->tablename;
00091         }
00092 
00093         function type() {
00094                 return $this->type;
00095         }
00096 
00097         function isNullable() {
00098                 return $this->nullable;
00099         }
00100 
00101         function maxLength() {
00102                 return $this->max_length;
00103         }
00104 
00105         function is_deferrable() {
00106                 return $this->deferrable;
00107         }
00108 
00109         function is_deferred() {
00110                 return $this->deferred;
00111         }
00112 
00113         function conname() {
00114                 return $this->conname;
00115         }
00119         function defaultValue() {
00120                 if( $this->has_default ) {
00121                         return $this->default;
00122                 } else {
00123                         return false;
00124                 }
00125         }
00126 
00127 }
00128 
00136 class PostgresTransactionState {
00137 
00138         static $WATCHED = array(
00139                 array(
00140                         "desc" => "%s: Connection state changed from %s -> %s\n",
00141                         "states" => array(
00142                                 PGSQL_CONNECTION_OK       => "OK",
00143                                 PGSQL_CONNECTION_BAD      => "BAD"
00144                         )
00145                 ),
00146                 array(
00147                         "desc" => "%s: Transaction state changed from %s -> %s\n",
00148                         "states" => array(
00149                                 PGSQL_TRANSACTION_IDLE    => "IDLE",
00150                                 PGSQL_TRANSACTION_ACTIVE  => "ACTIVE",
00151                                 PGSQL_TRANSACTION_INTRANS => "TRANS",
00152                                 PGSQL_TRANSACTION_INERROR => "ERROR",
00153                                 PGSQL_TRANSACTION_UNKNOWN => "UNKNOWN"
00154                         )
00155                 )
00156         );
00157 
00158         public function __construct( $conn ) {
00159                 $this->mConn = $conn;
00160                 $this->update();
00161                 $this->mCurrentState = $this->mNewState;
00162         }
00163 
00164         public function update() {
00165                 $this->mNewState = array(
00166                         pg_connection_status( $this->mConn ),
00167                         pg_transaction_status( $this->mConn )
00168                 );
00169         }
00170 
00171         public function check() {
00172                 global $wgDebugDBTransactions;
00173                 $this->update();
00174                 if ( $wgDebugDBTransactions ) {
00175                         if ( $this->mCurrentState !== $this->mNewState ) {
00176                                 $old = reset( $this->mCurrentState );
00177                                 $new = reset( $this->mNewState );
00178                                 foreach ( self::$WATCHED as $watched ) {
00179                                         if ($old !== $new) {
00180                                                 $this->log_changed($old, $new, $watched);
00181                                         }
00182                                         $old = next( $this->mCurrentState );
00183                                         $new = next( $this->mNewState );
00184 
00185                                 }
00186                         }
00187                 }
00188                 $this->mCurrentState = $this->mNewState;
00189         }
00190 
00191         protected function describe_changed( $status, $desc_table ) {
00192                 if( isset( $desc_table[$status] ) ) {
00193                         return $desc_table[$status];
00194                 } else {
00195                         return "STATUS " . $status;
00196                 }
00197         }
00198 
00199         protected function log_changed( $old, $new, $watched ) {
00200                 wfDebug(sprintf($watched["desc"],
00201                         $this->mConn,
00202                         $this->describe_changed( $old, $watched["states"] ),
00203                         $this->describe_changed( $new, $watched["states"] ))
00204                 );
00205         }
00206 }
00207 
00213 class SavepointPostgres {
00217         protected $dbw;
00218         protected $id;
00219         protected $didbegin;
00220 
00221         public function __construct ($dbw, $id) {
00222                 $this->dbw = $dbw;
00223                 $this->id = $id;
00224                 $this->didbegin = false;
00225                 /* If we are not in a transaction, we need to be for savepoint trickery */
00226                 if ( !$dbw->trxLevel() ) {
00227                                 $dbw->begin( "FOR SAVEPOINT" );
00228                                 $this->didbegin = true;
00229                 }
00230         }
00231 
00232         public function __destruct() {
00233                 if ( $this->didbegin ) {
00234                         $this->dbw->rollback();
00235                 }
00236         }
00237 
00238         public function commit() {
00239                 if ( $this->didbegin ) {
00240                         $this->dbw->commit();
00241                 }
00242         }
00243 
00244         protected function query( $keyword, $msg_ok, $msg_failed ) {
00245                 global $wgDebugDBTransactions;
00246                 if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
00247                         if ( $wgDebugDBTransactions ) {
00248                                 wfDebug( sprintf ($msg_ok, $this->id ) );
00249                         }
00250                 } else {
00251                         wfDebug( sprintf ($msg_failed, $this->id ) );
00252                 }
00253         }
00254 
00255         public function savepoint() {
00256                 $this->query("SAVEPOINT",
00257                         "Transaction state: savepoint \"%s\" established.\n",
00258                         "Transaction state: establishment of savepoint \"%s\" FAILED.\n"
00259                 );
00260         }
00261 
00262         public function release() {
00263                 $this->query("RELEASE",
00264                         "Transaction state: savepoint \"%s\" released.\n",
00265                         "Transaction state: release of savepoint \"%s\" FAILED.\n"
00266                 );
00267         }
00268 
00269         public function rollback() {
00270                 $this->query("ROLLBACK TO",
00271                         "Transaction state: savepoint \"%s\" rolled back.\n",
00272                         "Transaction state: rollback of savepoint \"%s\" FAILED.\n"
00273                 );
00274         }
00275 
00276         public function __toString() {
00277                 return (string)$this->id;
00278         }
00279 }
00280 
00284 class DatabasePostgres extends DatabaseBase {
00285         var $mInsertId = null;
00286         var $mLastResult = null;
00287         var $numeric_version = null;
00288         var $mAffectedRows = null;
00289 
00290         function getType() {
00291                 return 'postgres';
00292         }
00293 
00294         function cascadingDeletes() {
00295                 return true;
00296         }
00297         function cleanupTriggers() {
00298                 return true;
00299         }
00300         function strictIPs() {
00301                 return true;
00302         }
00303         function realTimestamps() {
00304                 return true;
00305         }
00306         function implicitGroupby() {
00307                 return false;
00308         }
00309         function implicitOrderby() {
00310                 return false;
00311         }
00312         function searchableIPs() {
00313                 return true;
00314         }
00315         function functionalIndexes() {
00316                 return true;
00317         }
00318 
00319         function hasConstraint( $name ) {
00320                 $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
00321                                 pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $this->getCoreSchema() ) ."'";
00322                 $res = $this->doQuery( $SQL );
00323                 return $this->numRows( $res );
00324         }
00325 
00330         function open( $server, $user, $password, $dbName ) {
00331                 # Test for Postgres support, to avoid suppressed fatal error
00332                 if ( !function_exists( 'pg_connect' ) ) {
00333                         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" );
00334                 }
00335 
00336                 global $wgDBport;
00337 
00338                 if ( !strlen( $user ) ) { # e.g. the class is being loaded
00339                         return;
00340                 }
00341 
00342                 $this->mServer = $server;
00343                 $port = $wgDBport;
00344                 $this->mUser = $user;
00345                 $this->mPassword = $password;
00346                 $this->mDBname = $dbName;
00347 
00348                 $connectVars = array(
00349                         'dbname' => $dbName,
00350                         'user' => $user,
00351                         'password' => $password
00352                 );
00353                 if ( $server != false && $server != '' ) {
00354                         $connectVars['host'] = $server;
00355                 }
00356                 if ( $port != false && $port != '' ) {
00357                         $connectVars['port'] = $port;
00358                 }
00359                 if ( $this->mFlags & DBO_SSL ) {
00360                         $connectVars['sslmode'] = 1;
00361                 }
00362 
00363                 $this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
00364                 $this->close();
00365                 $this->installErrorHandler();
00366                 $this->mConn = pg_connect( $this->connectString );
00367                 $phpError = $this->restoreErrorHandler();
00368 
00369                 if ( !$this->mConn ) {
00370                         wfDebug( "DB connection error\n" );
00371                         wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
00372                         wfDebug( $this->lastError() . "\n" );
00373                         throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
00374                 }
00375 
00376                 $this->mOpened = true;
00377                 $this->mTransactionState = new PostgresTransactionState( $this->mConn );
00378 
00379                 global $wgCommandLineMode;
00380                 # If called from the command-line (e.g. importDump), only show errors
00381                 if ( $wgCommandLineMode ) {
00382                         $this->doQuery( "SET client_min_messages = 'ERROR'" );
00383                 }
00384 
00385                 $this->query( "SET client_encoding='UTF8'", __METHOD__ );
00386                 $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
00387                 $this->query( "SET timezone = 'GMT'", __METHOD__ );
00388                 $this->query( "SET standard_conforming_strings = on", __METHOD__ );
00389 
00390                 global $wgDBmwschema;
00391                 $this->determineCoreSchema( $wgDBmwschema );
00392 
00393                 return $this->mConn;
00394         }
00395 
00401         function selectDB( $db ) {
00402                 if ( $this->mDBname !== $db ) {
00403                         return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
00404                 } else {
00405                         return true;
00406                 }
00407         }
00408 
00409         function makeConnectionString( $vars ) {
00410                 $s = '';
00411                 foreach ( $vars as $name => $value ) {
00412                         $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
00413                 }
00414                 return $s;
00415         }
00416 
00422         protected function closeConnection() {
00423                 return pg_close( $this->mConn );
00424         }
00425 
00426         public function doQuery( $sql ) {
00427                 if ( function_exists( 'mb_convert_encoding' ) ) {
00428                         $sql = mb_convert_encoding( $sql, 'UTF-8' );
00429                 }
00430                 $this->mTransactionState->check();
00431                 if( pg_send_query( $this->mConn, $sql ) === false ) {
00432                         throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
00433                 }
00434                 $this->mLastResult = pg_get_result( $this->mConn );
00435                 $this->mTransactionState->check();
00436                 $this->mAffectedRows = null;
00437                 if ( pg_result_error( $this->mLastResult ) ) {
00438                         return false;
00439                 }
00440                 return $this->mLastResult;
00441         }
00442 
00443         protected function dumpError () {
00444                 $diags = array( PGSQL_DIAG_SEVERITY,
00445                                 PGSQL_DIAG_SQLSTATE,
00446                                 PGSQL_DIAG_MESSAGE_PRIMARY,
00447                                 PGSQL_DIAG_MESSAGE_DETAIL,
00448                                 PGSQL_DIAG_MESSAGE_HINT,
00449                                 PGSQL_DIAG_STATEMENT_POSITION,
00450                                 PGSQL_DIAG_INTERNAL_POSITION,
00451                                 PGSQL_DIAG_INTERNAL_QUERY,
00452                                 PGSQL_DIAG_CONTEXT,
00453                                 PGSQL_DIAG_SOURCE_FILE,
00454                                 PGSQL_DIAG_SOURCE_LINE,
00455                                 PGSQL_DIAG_SOURCE_FUNCTION );
00456                 foreach ( $diags as $d ) {
00457                         wfDebug( sprintf("PgSQL ERROR(%d): %s\n", $d, pg_result_error_field( $this->mLastResult, $d ) ) );
00458                 }
00459         }
00460 
00461         function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
00462                 /* Transaction stays in the ERROR state until rolledback */
00463                 if ( $tempIgnore ) {
00464                         /* Check for constraint violation */
00465                         if ( $errno === '23505' ) {
00466                                 parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
00467                                 return;
00468                         }
00469                 }
00470                 /* Don't ignore serious errors */
00471                 $this->rollback( __METHOD__ );
00472                 parent::reportQueryError( $error, $errno, $sql, $fname, false );
00473         }
00474 
00475 
00476         function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
00477                 return $this->query( $sql, $fname, true );
00478         }
00479 
00480         function freeResult( $res ) {
00481                 if ( $res instanceof ResultWrapper ) {
00482                         $res = $res->result;
00483                 }
00484                 wfSuppressWarnings();
00485                 $ok = pg_free_result( $res );
00486                 wfRestoreWarnings();
00487                 if ( !$ok ) {
00488                         throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
00489                 }
00490         }
00491 
00492         function fetchObject( $res ) {
00493                 if ( $res instanceof ResultWrapper ) {
00494                         $res = $res->result;
00495                 }
00496                 wfSuppressWarnings();
00497                 $row = pg_fetch_object( $res );
00498                 wfRestoreWarnings();
00499                 # @todo FIXME: HACK HACK HACK HACK debug
00500 
00501                 # @todo hashar: not sure if the following test really trigger if the object
00502                 #          fetching failed.
00503                 if( pg_last_error( $this->mConn ) ) {
00504                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00505                 }
00506                 return $row;
00507         }
00508 
00509         function fetchRow( $res ) {
00510                 if ( $res instanceof ResultWrapper ) {
00511                         $res = $res->result;
00512                 }
00513                 wfSuppressWarnings();
00514                 $row = pg_fetch_array( $res );
00515                 wfRestoreWarnings();
00516                 if( pg_last_error( $this->mConn ) ) {
00517                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00518                 }
00519                 return $row;
00520         }
00521 
00522         function numRows( $res ) {
00523                 if ( $res instanceof ResultWrapper ) {
00524                         $res = $res->result;
00525                 }
00526                 wfSuppressWarnings();
00527                 $n = pg_num_rows( $res );
00528                 wfRestoreWarnings();
00529                 if( pg_last_error( $this->mConn ) ) {
00530                         throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
00531                 }
00532                 return $n;
00533         }
00534 
00535         function numFields( $res ) {
00536                 if ( $res instanceof ResultWrapper ) {
00537                         $res = $res->result;
00538                 }
00539                 return pg_num_fields( $res );
00540         }
00541 
00542         function fieldName( $res, $n ) {
00543                 if ( $res instanceof ResultWrapper ) {
00544                         $res = $res->result;
00545                 }
00546                 return pg_field_name( $res, $n );
00547         }
00548 
00553         function insertId() {
00554                 return $this->mInsertId;
00555         }
00556 
00557         function dataSeek( $res, $row ) {
00558                 if ( $res instanceof ResultWrapper ) {
00559                         $res = $res->result;
00560                 }
00561                 return pg_result_seek( $res, $row );
00562         }
00563 
00564         function lastError() {
00565                 if ( $this->mConn ) {
00566                         if ( $this->mLastResult ) {
00567                                 return pg_result_error( $this->mLastResult );
00568                         } else {
00569                                 return pg_last_error();
00570                         }
00571                 } else {
00572                         return 'No database connection';
00573                 }
00574         }
00575         function lastErrno() {
00576                 if ( $this->mLastResult ) {
00577                         return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
00578                 } else {
00579                         return false;
00580                 }
00581         }
00582 
00583         function affectedRows() {
00584                 if ( !is_null( $this->mAffectedRows ) ) {
00585                         // Forced result for simulated queries
00586                         return $this->mAffectedRows;
00587                 }
00588                 if( empty( $this->mLastResult ) ) {
00589                         return 0;
00590                 }
00591                 return pg_affected_rows( $this->mLastResult );
00592         }
00593 
00602         function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
00603                 $options['EXPLAIN'] = true;
00604                 $res = $this->select( $table, $vars, $conds, $fname, $options );
00605                 $rows = -1;
00606                 if ( $res ) {
00607                         $row = $this->fetchRow( $res );
00608                         $count = array();
00609                         if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
00610                                 $rows = $count[1];
00611                         }
00612                 }
00613                 return $rows;
00614         }
00615 
00621         function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) {
00622                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
00623                 $res = $this->query( $sql, $fname );
00624                 if ( !$res ) {
00625                         return null;
00626                 }
00627                 foreach ( $res as $row ) {
00628                         if ( $row->indexname == $this->indexName( $index ) ) {
00629                                 return $row;
00630                         }
00631                 }
00632                 return false;
00633         }
00634 
00641         function indexAttributes ( $index, $schema = false ) {
00642                 if ( $schema === false )
00643                         $schema = $this->getCoreSchema();
00644                 /*
00645                  * A subquery would be not needed if we didn't care about the order
00646                  * of attributes, but we do
00647                  */
00648                 $sql = <<<__INDEXATTR__
00649 
00650                         SELECT opcname,
00651                                 attname,
00652                                 i.indoption[s.g] as option,
00653                                 pg_am.amname
00654                         FROM
00655                                 (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
00656                                         FROM
00657                                                 pg_index isub
00658                                         JOIN pg_class cis
00659                                                 ON cis.oid=isub.indexrelid
00660                                         JOIN pg_namespace ns
00661                                                 ON cis.relnamespace = ns.oid
00662                                         WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
00663                                 pg_attribute,
00664                                 pg_opclass opcls,
00665                                 pg_am,
00666                                 pg_class ci
00667                                 JOIN pg_index i
00668                                         ON ci.oid=i.indexrelid
00669                                 JOIN pg_class ct
00670                                         ON ct.oid = i.indrelid
00671                                 JOIN pg_namespace n
00672                                         ON ci.relnamespace = n.oid
00673                                 WHERE
00674                                         ci.relname='$index' AND n.nspname='$schema'
00675                                         AND     attrelid = ct.oid
00676                                         AND     i.indkey[s.g] = attnum
00677                                         AND     i.indclass[s.g] = opcls.oid
00678                                         AND     pg_am.oid = opcls.opcmethod
00679 __INDEXATTR__;
00680                 $res = $this->query($sql, __METHOD__);
00681                 $a = array();
00682                 if ( $res ) {
00683                         foreach ( $res as $row ) {
00684                                 $a[] = array(
00685                                         $row->attname,
00686                                         $row->opcname,
00687                                         $row->amname,
00688                                         $row->option);
00689                         }
00690                 } else {
00691                         return null;
00692                 }
00693                 return $a;
00694         }
00695 
00696 
00697         function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
00698                 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
00699                         " AND indexdef LIKE 'CREATE UNIQUE%(" .
00700                         $this->strencode( $this->indexName( $index ) ) .
00701                         ")'";
00702                 $res = $this->query( $sql, $fname );
00703                 if ( !$res ) {
00704                         return null;
00705                 }
00706                 foreach ( $res as $row ) {
00707                         return true;
00708                 }
00709                 return false;
00710         }
00711 
00725         function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
00726                 if ( !count( $args ) ) {
00727                         return true;
00728                 }
00729 
00730                 $table = $this->tableName( $table );
00731                 if (! isset( $this->numeric_version ) ) {
00732                         $this->getServerVersion();
00733                 }
00734 
00735                 if ( !is_array( $options ) ) {
00736                         $options = array( $options );
00737                 }
00738 
00739                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
00740                         $multi = true;
00741                         $keys = array_keys( $args[0] );
00742                 } else {
00743                         $multi = false;
00744                         $keys = array_keys( $args );
00745                 }
00746 
00747                 // If IGNORE is set, we use savepoints to emulate mysql's behavior
00748                 $savepoint = null;
00749                 if ( in_array( 'IGNORE', $options ) ) {
00750                         $savepoint = new SavepointPostgres( $this, 'mw' );
00751                         $olde = error_reporting( 0 );
00752                         // For future use, we may want to track the number of actual inserts
00753                         // Right now, insert (all writes) simply return true/false
00754                         $numrowsinserted = 0;
00755                 }
00756 
00757                 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
00758 
00759                 if ( $multi ) {
00760                         if ( $this->numeric_version >= 8.2 && !$savepoint ) {
00761                                 $first = true;
00762                                 foreach ( $args as $row ) {
00763                                         if ( $first ) {
00764                                                 $first = false;
00765                                         } else {
00766                                                 $sql .= ',';
00767                                         }
00768                                         $sql .= '(' . $this->makeList( $row ) . ')';
00769                                 }
00770                                 $res = (bool)$this->query( $sql, $fname, $savepoint );
00771                         } else {
00772                                 $res = true;
00773                                 $origsql = $sql;
00774                                 foreach ( $args as $row ) {
00775                                         $tempsql = $origsql;
00776                                         $tempsql .= '(' . $this->makeList( $row ) . ')';
00777 
00778                                         if ( $savepoint ) {
00779                                                 $savepoint->savepoint();
00780                                         }
00781 
00782                                         $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
00783 
00784                                         if ( $savepoint ) {
00785                                                 $bar = pg_last_error();
00786                                                 if ( $bar != false ) {
00787                                                         $savepoint->rollback();
00788                                                 } else {
00789                                                         $savepoint->release();
00790                                                         $numrowsinserted++;
00791                                                 }
00792                                         }
00793 
00794                                         // If any of them fail, we fail overall for this function call
00795                                         // Note that this will be ignored if IGNORE is set
00796                                         if ( !$tempres ) {
00797                                                 $res = false;
00798                                         }
00799                                 }
00800                         }
00801                 } else {
00802                         // Not multi, just a lone insert
00803                         if ( $savepoint ) {
00804                                 $savepoint->savepoint();
00805                         }
00806 
00807                         $sql .= '(' . $this->makeList( $args ) . ')';
00808                         $res = (bool)$this->query( $sql, $fname, $savepoint );
00809                         if ( $savepoint ) {
00810                                 $bar = pg_last_error();
00811                                 if ( $bar != false ) {
00812                                         $savepoint->rollback();
00813                                 } else {
00814                                         $savepoint->release();
00815                                         $numrowsinserted++;
00816                                 }
00817                         }
00818                 }
00819                 if ( $savepoint ) {
00820                         $olde = error_reporting( $olde );
00821                         $savepoint->commit();
00822 
00823                         // Set the affected row count for the whole operation
00824                         $this->mAffectedRows = $numrowsinserted;
00825 
00826                         // IGNORE always returns true
00827                         return true;
00828                 }
00829 
00830                 return $res;
00831         }
00832 
00842         function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
00843                 $insertOptions = array(), $selectOptions = array() )
00844         {
00845                 $destTable = $this->tableName( $destTable );
00846 
00847                 if( !is_array( $insertOptions ) ) {
00848                         $insertOptions = array( $insertOptions );
00849                 }
00850 
00851                 /*
00852                  * If IGNORE is set, we use savepoints to emulate mysql's behavior
00853                  * Ignore LOW PRIORITY option, since it is MySQL-specific
00854                  */
00855                 $savepoint = null;
00856                 if ( in_array( 'IGNORE', $insertOptions ) ) {
00857                         $savepoint = new SavepointPostgres( $this, 'mw' );
00858                         $olde = error_reporting( 0 );
00859                         $numrowsinserted = 0;
00860                         $savepoint->savepoint();
00861                 }
00862 
00863                 if( !is_array( $selectOptions ) ) {
00864                         $selectOptions = array( $selectOptions );
00865                 }
00866                 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
00867                 if( is_array( $srcTable ) ) {
00868                         $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
00869                 } else {
00870                         $srcTable = $this->tableName( $srcTable );
00871                 }
00872 
00873                 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
00874                                 " SELECT $startOpts " . implode( ',', $varMap ) .
00875                                 " FROM $srcTable $useIndex";
00876 
00877                 if ( $conds != '*' ) {
00878                         $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
00879                 }
00880 
00881                 $sql .= " $tailOpts";
00882 
00883                 $res = (bool)$this->query( $sql, $fname, $savepoint );
00884                 if( $savepoint ) {
00885                         $bar = pg_last_error();
00886                         if( $bar != false ) {
00887                                 $savepoint->rollback();
00888                         } else {
00889                                 $savepoint->release();
00890                                 $numrowsinserted++;
00891                         }
00892                         $olde = error_reporting( $olde );
00893                         $savepoint->commit();
00894 
00895                         // Set the affected row count for the whole operation
00896                         $this->mAffectedRows = $numrowsinserted;
00897 
00898                         // IGNORE always returns true
00899                         return true;
00900                 }
00901 
00902                 return $res;
00903         }
00904 
00905         function tableName( $name, $format = 'quoted' ) {
00906                 # Replace reserved words with better ones
00907                 switch( $name ) {
00908                         case 'user':
00909                                 return $this->realTableName( 'mwuser', $format );
00910                         case 'text':
00911                                 return $this->realTableName( 'pagecontent', $format );
00912                         default:
00913                                 return $this->realTableName( $name, $format );
00914                 }
00915         }
00916 
00917         /* Don't cheat on installer */
00918         function realTableName( $name, $format = 'quoted' ) {
00919                 return parent::tableName( $name, $format );
00920         }
00921 
00926         function nextSequenceValue( $seqName ) {
00927                 $safeseq = str_replace( "'", "''", $seqName );
00928                 $res = $this->query( "SELECT nextval('$safeseq')" );
00929                 $row = $this->fetchRow( $res );
00930                 $this->mInsertId = $row[0];
00931                 return $this->mInsertId;
00932         }
00933 
00938         function currentSequenceValue( $seqName ) {
00939                 $safeseq = str_replace( "'", "''", $seqName );
00940                 $res = $this->query( "SELECT currval('$safeseq')" );
00941                 $row = $this->fetchRow( $res );
00942                 $currval = $row[0];
00943                 return $currval;
00944         }
00945 
00946         # Returns the size of a text field, or -1 for "unlimited"
00947         function textFieldSize( $table, $field ) {
00948                 $table = $this->tableName( $table );
00949                 $sql = "SELECT t.typname as ftype,a.atttypmod as size
00950                         FROM pg_class c, pg_attribute a, pg_type t
00951                         WHERE relname='$table' AND a.attrelid=c.oid AND
00952                                 a.atttypid=t.oid and a.attname='$field'";
00953                 $res =$this->query( $sql );
00954                 $row = $this->fetchObject( $res );
00955                 if ( $row->ftype == 'varchar' ) {
00956                         $size = $row->size - 4;
00957                 } else {
00958                         $size = $row->size;
00959                 }
00960                 return $size;
00961         }
00962 
00963         function limitResult( $sql, $limit, $offset = false ) {
00964                 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
00965         }
00966 
00967         function wasDeadlock() {
00968                 return $this->lastErrno() == '40P01';
00969         }
00970 
00971         function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) {
00972                 $newName = $this->addIdentifierQuotes( $newName );
00973                 $oldName = $this->addIdentifierQuotes( $oldName );
00974                 return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
00975         }
00976 
00977         function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) {
00978                 $eschema = $this->addQuotes( $this->getCoreSchema() );
00979                 $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
00980                 $endArray = array();
00981 
00982                 foreach( $result as $table ) {
00983                         $vars = get_object_vars($table);
00984                         $table = array_pop( $vars );
00985                         if( !$prefix || strpos( $table, $prefix ) === 0 ) {
00986                                 $endArray[] = $table;
00987                         }
00988                 }
00989 
00990                 return $endArray;
00991         }
00992 
00993         function timestamp( $ts = 0 ) {
00994                 return wfTimestamp( TS_POSTGRES, $ts );
00995         }
00996 
00997         /*
00998          * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12
00999          * to http://www.php.net/manual/en/ref.pgsql.php
01000          *
01001          * Parsing a postgres array can be a tricky problem, he's my
01002          * take on this, it handles multi-dimensional arrays plus
01003          * escaping using a nasty regexp to determine the limits of each
01004          * data-item.
01005          *
01006          * This should really be handled by PHP PostgreSQL module
01007          *
01008          * @since 1.19
01009          * @param $text   string: postgreql array returned in a text form like {a,b}
01010          * @param $output string
01011          * @param $limit  int
01012          * @param $offset int
01013          * @return string
01014          */
01015         function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
01016                 if( false === $limit ) {
01017                         $limit = strlen( $text )-1;
01018                         $output = array();
01019                 }
01020                 if( '{}' == $text ) {
01021                         return $output;
01022                 }
01023                 do {
01024                         if ( '{' != $text{$offset} ) {
01025                                 preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
01026                                         $text, $match, 0, $offset );
01027                                 $offset += strlen( $match[0] );
01028                                 $output[] = ( '"' != $match[1]{0}
01029                                                 ? $match[1]
01030                                                 : stripcslashes( substr( $match[1], 1, -1 ) ) );
01031                                 if ( '},' == $match[3] ) {
01032                                         return $output;
01033                                 }
01034                         } else {
01035                                 $offset = $this->pg_array_parse( $text, $output, $limit, $offset+1 );
01036                         }
01037                 } while ( $limit > $offset );
01038                 return $output;
01039         }
01040 
01044         public function aggregateValue( $valuedata, $valuename = 'value' ) {
01045                 return $valuedata;
01046         }
01047 
01051         public static function getSoftwareLink() {
01052                 return '[http://www.postgresql.org/ PostgreSQL]';
01053         }
01054 
01055 
01063         function getCurrentSchema() {
01064                 $res = $this->query( "SELECT current_schema()", __METHOD__);
01065                 $row = $this->fetchRow( $res );
01066                 return $row[0];
01067         }
01068 
01079         function getSchemas() {
01080                 $res = $this->query( "SELECT current_schemas(false)", __METHOD__);
01081                 $row = $this->fetchRow( $res );
01082                 $schemas = array();
01083                 /* PHP pgsql support does not support array type, "{a,b}" string is returned */
01084                 return $this->pg_array_parse($row[0], $schemas);
01085         }
01086 
01096         function getSearchPath() {
01097                 $res = $this->query( "SHOW search_path", __METHOD__);
01098                 $row = $this->fetchRow( $res );
01099                 /* PostgreSQL returns SHOW values as strings */
01100                 return explode(",", $row[0]);
01101         }
01102 
01110         function setSearchPath( $search_path ) {
01111                 $this->query( "SET search_path = " . implode(", ", $search_path) );
01112         }
01113 
01127         function determineCoreSchema( $desired_schema ) {
01128                 $this->begin( __METHOD__ );
01129                 if ( $this->schemaExists( $desired_schema ) ) {
01130                         if ( in_array( $desired_schema, $this->getSchemas() ) ) {
01131                                 $this->mCoreSchema = $desired_schema;
01132                                 wfDebug("Schema \"" . $desired_schema . "\" already in the search path\n");
01133                         } else {
01139                                 $search_path = $this->getSearchPath();
01140                                 array_unshift( $search_path,
01141                                         $this->addIdentifierQuotes( $desired_schema ));
01142                                 $this->setSearchPath( $search_path );
01143                                 $this->mCoreSchema = $desired_schema;
01144                                 wfDebug("Schema \"" . $desired_schema . "\" added to the search path\n");
01145                         }
01146                 } else {
01147                         $this->mCoreSchema = $this->getCurrentSchema();
01148                         wfDebug("Schema \"" . $desired_schema . "\" not found, using current \"". $this->mCoreSchema ."\"\n");
01149                 }
01150                 /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
01151                 $this->commit( __METHOD__ );
01152         }
01153 
01160         function getCoreSchema() {
01161                 return $this->mCoreSchema;
01162         }
01163 
01167         function getServerVersion() {
01168                 if ( !isset( $this->numeric_version ) ) {
01169                         $versionInfo = pg_version( $this->mConn );
01170                         if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
01171                                 // Old client, abort install
01172                                 $this->numeric_version = '7.3 or earlier';
01173                         } elseif ( isset( $versionInfo['server'] ) ) {
01174                                 // Normal client
01175                                 $this->numeric_version = $versionInfo['server'];
01176                         } else {
01177                                 // Bug 16937: broken pgsql extension from PHP<5.3
01178                                 $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
01179                         }
01180                 }
01181                 return $this->numeric_version;
01182         }
01183 
01189         function relationExists( $table, $types, $schema = false ) {
01190                 if ( !is_array( $types ) ) {
01191                         $types = array( $types );
01192                 }
01193                 if ( !$schema ) {
01194                         $schema = $this->getCoreSchema();
01195                 }
01196                 $table = $this->realTableName( $table, 'raw' );
01197                 $etable = $this->addQuotes( $table );
01198                 $eschema = $this->addQuotes( $schema );
01199                 $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
01200                         . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
01201                         . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
01202                 $res = $this->query( $SQL );
01203                 $count = $res ? $res->numRows() : 0;
01204                 return (bool)$count;
01205         }
01206 
01212         function tableExists( $table, $fname = __METHOD__, $schema = false ) {
01213                 return $this->relationExists( $table, array( 'r', 'v' ), $schema );
01214         }
01215 
01216         function sequenceExists( $sequence, $schema = false ) {
01217                 return $this->relationExists( $sequence, 'S', $schema );
01218         }
01219 
01220         function triggerExists( $table, $trigger ) {
01221                 $q = <<<SQL
01222         SELECT 1 FROM pg_class, pg_namespace, pg_trigger
01223                 WHERE relnamespace=pg_namespace.oid AND relkind='r'
01224                           AND tgrelid=pg_class.oid
01225                           AND nspname=%s AND relname=%s AND tgname=%s
01226 SQL;
01227                 $res = $this->query(
01228                         sprintf(
01229                                 $q,
01230                                 $this->addQuotes( $this->getCoreSchema() ),
01231                                 $this->addQuotes( $table ),
01232                                 $this->addQuotes( $trigger )
01233                         )
01234                 );
01235                 if ( !$res ) {
01236                         return null;
01237                 }
01238                 $rows = $res->numRows();
01239                 return $rows;
01240         }
01241 
01242         function ruleExists( $table, $rule ) {
01243                 $exists = $this->selectField( 'pg_rules', 'rulename',
01244                         array(
01245                                 'rulename' => $rule,
01246                                 'tablename' => $table,
01247                                 'schemaname' => $this->getCoreSchema()
01248                         )
01249                 );
01250                 return $exists === $rule;
01251         }
01252 
01253         function constraintExists( $table, $constraint ) {
01254                 $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
01255                            "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
01256                         $this->addQuotes( $this->getCoreSchema() ),
01257                         $this->addQuotes( $table ),
01258                         $this->addQuotes( $constraint )
01259                 );
01260                 $res = $this->query( $SQL );
01261                 if ( !$res ) {
01262                         return null;
01263                 }
01264                 $rows = $res->numRows();
01265                 return $rows;
01266         }
01267 
01272         function schemaExists( $schema ) {
01273                 $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
01274                         array( 'nspname' => $schema ), __METHOD__ );
01275                 return (bool)$exists;
01276         }
01277 
01282         function roleExists( $roleName ) {
01283                 $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
01284                         array( 'rolname' => $roleName ), __METHOD__ );
01285                 return (bool)$exists;
01286         }
01287 
01288         function fieldInfo( $table, $field ) {
01289                 return PostgresField::fromText( $this, $table, $field );
01290         }
01291 
01296         function fieldType( $res, $index ) {
01297                 if ( $res instanceof ResultWrapper ) {
01298                         $res = $res->result;
01299                 }
01300                 return pg_field_type( $res, $index );
01301         }
01302 
01307         function encodeBlob( $b ) {
01308                 return new Blob( pg_escape_bytea( $this->mConn, $b ) );
01309         }
01310 
01311         function decodeBlob( $b ) {
01312                 if ( $b instanceof Blob ) {
01313                         $b = $b->fetch();
01314                 }
01315                 return pg_unescape_bytea( $b );
01316         }
01317 
01318         function strencode( $s ) { # Should not be called by us
01319                 return pg_escape_string( $this->mConn, $s );
01320         }
01321 
01326         function addQuotes( $s ) {
01327                 if ( is_null( $s ) ) {
01328                         return 'NULL';
01329                 } elseif ( is_bool( $s ) ) {
01330                         return intval( $s );
01331                 } elseif ( $s instanceof Blob ) {
01332                         return "'" . $s->fetch( $s ) . "'";
01333                 }
01334                 return "'" . pg_escape_string( $this->mConn, $s ) . "'";
01335         }
01336 
01347         protected function replaceVars( $ins ) {
01348                 $ins = parent::replaceVars( $ins );
01349 
01350                 if ( $this->numeric_version >= 8.3 ) {
01351                         // Thanks for not providing backwards-compatibility, 8.3
01352                         $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
01353                 }
01354 
01355                 if ( $this->numeric_version <= 8.1 ) { // Our minimum version
01356                         $ins = str_replace( 'USING gin', 'USING gist', $ins );
01357                 }
01358 
01359                 return $ins;
01360         }
01361 
01371         function makeSelectOptions( $options ) {
01372                 $preLimitTail = $postLimitTail = '';
01373                 $startOpts = $useIndex = '';
01374 
01375                 $noKeyOptions = array();
01376                 foreach ( $options as $key => $option ) {
01377                         if ( is_numeric( $key ) ) {
01378                                 $noKeyOptions[$option] = true;
01379                         }
01380                 }
01381 
01382                 if ( isset( $options['GROUP BY'] ) ) {
01383                         $gb = is_array( $options['GROUP BY'] )
01384                                 ? implode( ',', $options['GROUP BY'] )
01385                                 : $options['GROUP BY'];
01386                         $preLimitTail .= " GROUP BY {$gb}";
01387                 }
01388 
01389                 if ( isset( $options['HAVING'] ) ) {
01390                         $preLimitTail .= " HAVING {$options['HAVING']}";
01391                 }
01392 
01393                 if ( isset( $options['ORDER BY'] ) ) {
01394                         $ob = is_array( $options['ORDER BY'] )
01395                                 ? implode( ',', $options['ORDER BY'] )
01396                                 : $options['ORDER BY'];
01397                         $preLimitTail .= " ORDER BY {$ob}";
01398                 }
01399 
01400                 //if ( isset( $options['LIMIT'] ) ) {
01401                 //      $tailOpts .= $this->limitResult( '', $options['LIMIT'],
01402                 //              isset( $options['OFFSET'] ) ? $options['OFFSET']
01403                 //              : false );
01404                 //}
01405 
01406                 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
01407                         $postLimitTail .= ' FOR UPDATE';
01408                 }
01409                 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
01410                         $startOpts .= 'DISTINCT';
01411                 }
01412 
01413                 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
01414         }
01415 
01416         function setFakeMaster( $enabled = true ) {}
01417 
01418         function getDBname() {
01419                 return $this->mDBname;
01420         }
01421 
01422         function getServer() {
01423                 return $this->mServer;
01424         }
01425 
01426         function buildConcat( $stringList ) {
01427                 return implode( ' || ', $stringList );
01428         }
01429 
01430         public function getSearchEngine() {
01431                 return 'SearchPostgres';
01432         }
01433 
01434         public function streamStatementEnd( &$sql, &$newLine ) {
01435                 # Allow dollar quoting for function declarations
01436                 if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
01437                         if ( $this->delimiter ) {
01438                                 $this->delimiter = false;
01439                         }
01440                         else {
01441                                 $this->delimiter = ';';
01442                         }
01443                 }
01444                 return parent::streamStatementEnd( $sql, $newLine );
01445         }
01446 } // end DatabasePostgres class