<?php
# $Id$
-#
-# DO NOT USE !!! Unless you want to help developping it.
-#
-# This file is an attempt to port the mysql database layer to postgreSQL. The
-# only thing done so far is s/mysql/pg/ and dieing if function haven't been
-# ported.
-#
-# As said brion 07/06/2004 :
-# "table definitions need to be changed. fulltext index needs to work differently
-# things that use the last insert id need to be changed. Probably other things
-# need to be changed. various semantics may be different."
-#
-# Hashar
+/**
+ * DO NOT USE !!! Unless you want to help developping it.
+ *
+ * This file is an attempt to port the mysql database layer to postgreSQL. The
+ * only thing done so far is s/mysql/pg/ and dieing if function haven't been
+ * ported.
+ *
+ * As said brion 07/06/2004 :
+ * "table definitions need to be changed. fulltext index needs to work differently
+ * things that use the last insert id need to be changed. Probably other things
+ * need to be changed. various semantics may be different."
+ *
+ * Hashar
+ *
+ * @package MediaWiki
+ */
+
+/**
+ * Depends on database
+ */
+require_once( 'Database.php' );
+
+/**
+ *
+ * @package MediaWiki
+ */
class DatabasePgsql extends Database {
var $mInsertId = NULL;
+ var $mLastResult = NULL;
function DatabasePgsql($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $debug = false, $bufferResults = true, $ignoreErrors = false)
+ $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
{
- Database::Database( $server, $user, $password, $dbName, $failFunction, $debug,
- $bufferResults, $ignoreErrors );
+ Database::Database( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
}
- /* static */ function newFromParams( $server, $user, $password, $dbName,
- $failFunction = false, $debug = false, $bufferResults = true, $ignoreErrors = false )
+ /* static */ function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
+ $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
{
- return new DatabasePgsql( $server, $user, $password, $dbName, $failFunction, $debug,
- $bufferResults, $ignoreErrors );
+ return new DatabasePgsql( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
}
- # Usually aborts on failure
- # If the failFunction is set to a non-zero integer, returns success
- function open( $server, $user, $password, $dbName )
- {
+ /**
+ * Usually aborts on failure
+ * If the failFunction is set to a non-zero integer, returns success
+ */
+ function open( $server, $user, $password, $dbName ) {
# Test for PostgreSQL support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
die( "PostgreSQL functions missing, have you compiled PHP with the --with-pgsql option?\n" );
}
+ global $wgDBschema;
+
$this->close();
$this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
+ $this->mSchemas = array($wgDBschema,'public');
$success = false;
- if ( "" != $dbName ) {
+ if ( '' != $dbName ) {
# start a database connection
- @$this->mConn = pg_connect("host=$server dbname=$dbName user=$user password=$password");
+ $hstring="";
+ if ($server!=false && $server!="") {
+ $hstring="host=$server ";
+ }
+ @$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password");
if ( $this->mConn == false ) {
wfDebug( "DB connection error\n" );
wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
wfDebug( $this->lastError()."\n" );
} else {
+ $this->setSchema();
$this->mOpened = true;
}
}
return $this->mConn;
}
- # Closes a database connection, if it is open
- # Returns success, true if already closed
- function close()
- {
+ /**
+ * Closes a database connection, if it is open
+ * Returns success, true if already closed
+ */
+ function close() {
$this->mOpened = false;
if ( $this->mConn ) {
return pg_close( $this->mConn );
}
}
- # Usually aborts on failure
- # If errors are explicitly ignored, returns success
- function query( $sql, $fname = "", $tempIgnore = false )
- {
- global $wgProfiling;
-
- if ( $wgProfiling ) {
- # generalizeSQL will probably cut down the query to reasonable
- # logging size most of the time. The substr is really just a sanity check.
- $profName = "query: " . substr( Database::generalizeSQL( $sql ), 0, 255 );
- wfProfileIn( $profName );
- }
-
- $this->mLastQuery = $sql;
-
- if ( $this->mDebug ) {
- $sqlx = substr( $sql, 0, 500 );
- $sqlx = wordwrap(strtr($sqlx,"\t\n"," "));
- wfDebug( "SQL: $sqlx\n" );
- }
-
- $ret = pg_query( $this->mConn , $sql);
- $this->mLastResult = $ret;
- if ( false == $ret ) {
- // Ignore errors during error handling to prevent infinite recursion
- $ignore = $this->setIgnoreErrors( true );
- $error = pg_last_error( $this->mConn );
- // TODO FIXME : no error number function in postgre
- // $errno = mysql_errno( $this->mConn );
- if( $ignore || $tempIgnore ) {
- wfDebug("SQL ERROR (ignored): " . $error . "\n");
- } else {
- wfDebug("SQL ERROR: " . $error . "\n");
- if ( $this->mOut ) {
- // this calls wfAbruptExit()
- $this->mOut->databaseError( $fname, $sql, $error, 0 );
- }
- }
- $this->setIgnoreErrors( $ignore );
- }
-
- if ( $wgProfiling ) {
- wfProfileOut( $profName );
- }
- return $ret;
+ function doQuery( $sql ) {
+ return $this->mLastResult=pg_query( $this->mConn , $sql);
}
-
- function queryIgnore( $sql, $fname = "" ) {
+
+ function queryIgnore( $sql, $fname = '' ) {
return $this->query( $sql, $fname, true );
}
wfDebugDieBacktrace( "Unable to free PostgreSQL result\n" );
}
}
+
function fetchObject( $res ) {
@$row = pg_fetch_object( $res );
# FIXME: HACK HACK HACK HACK debug
# hashar : not sure if the following test really trigger if the object
# fetching failled.
if( pg_last_error($this->mConn) ) {
- wfDebugDieBacktrace( "SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
+ wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
}
return $row;
}
function fetchRow( $res ) {
@$row = pg_fetch_array( $res );
if( pg_last_error($this->mConn) ) {
- wfDebugDieBacktrace( "SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
+ wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
}
return $row;
}
function numRows( $res ) {
@$n = pg_num_rows( $res );
if( pg_last_error($this->mConn) ) {
- wfDebugDieBacktrace( "SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
+ wfDebugDieBacktrace( 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
}
return $n;
}
function numFields( $res ) { return pg_num_fields( $res ); }
function fieldName( $res, $n ) { return pg_field_name( $res, $n ); }
- # This must be called after nextSequenceVal
+ /**
+ * This must be called after nextSequenceVal
+ */
function insertId() {
return $this->mInsertId;
}
function dataSeek( $res, $row ) { return pg_result_seek( $res, $row ); }
function lastError() { return pg_last_error(); }
+ function lastErrno() { return 1; }
+
function affectedRows() {
return pg_affected_rows( $this->mLastResult );
}
- # Returns information about an index
- # If errors are explicitly ignored, returns NULL on failure
- function indexInfo( $table, $index, $fname = "Database::indexExists" )
- {
+ /**
+ * Returns information about an index
+ * If errors are explicitly ignored, returns NULL on failure
+ */
+ function indexInfo( $table, $index, $fname = 'Database::indexExists' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
return false;
}
- function fieldInfo( $table, $field )
- {
- wfDebugDieBacktrace( "Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre" );
+ function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
+ $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
+ " AND indexdef LIKE 'CREATE UNIQUE%({$index})'";
+ $res = $this->query( $sql, $fname );
+ if ( !$res )
+ return NULL;
+ while ($row = $this->fetchObject( $res ))
+ return true;
+ return false;
+
+ }
+
+ function fieldInfo( $table, $field ) {
+ wfDebugDieBacktrace( 'Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre' );
/*
$res = $this->query( "SELECT * FROM '$table' LIMIT 1" );
$n = pg_num_fields( $res );
return false;*/
}
- function insertArray( $table, $a, $fname = "Database::insertArray", $options = array() ) {
+ function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
# PostgreSQL doesn't support options
- # We have a go at faking some of them
- if ( in_array( 'IGNORE', $options ) ) {
- $ignore = true;
- $oldIgnore = $this->setIgnoreErrors( true );
+ # We have a go at faking one of them
+ # TODO: DELAYED, LOW_PRIORITY
+
+ if ( !is_array($options))
+ $options = array($options);
+
+ if ( in_array( 'IGNORE', $options ) )
+ $oldIgnore = $this->ignoreErrors( true );
+
+ # IGNORE is performed using single-row inserts, ignoring errors in each
+ # FIXME: need some way to distiguish between key collision and other types of error
+ $oldIgnore = $this->ignoreErrors( true );
+ if ( !is_array( reset( $a ) ) ) {
+ $a = array( $a );
}
- $retVal = parent::insertArray( $table, $a, $fname, array() );
- if ( $ignore ) {
- $this->setIgnoreErrors( $oldIgnore );
+ foreach ( $a as $row ) {
+ parent::insert( $table, $row, $fname, array() );
}
+ $this->ignoreErrors( $oldIgnore );
+ $retVal = true;
+
+ if ( in_array( 'IGNORE', $options ) )
+ $this->ignoreErrors( $oldIgnore );
+
return $retVal;
}
function startTimer( $timeout )
{
global $IP;
- wfDebugDieBacktrace( "Database::startTimer() error : mysql_thread_id() not implemented for postgre" );
+ wfDebugDieBacktrace( 'Database::startTimer() error : mysql_thread_id() not implemented for postgre' );
/*$tid = mysql_thread_id( $this->mConn );
exec( "php $IP/killthread.php $timeout $tid &>/dev/null &" );*/
}
# First run any transformations from the parent object
$name = parent::tableName( $name );
+ # Replace backticks into double quotes
+ $name = strtr($name,'`','"');
+
# Now quote PG reserved keywords
switch( $name ) {
case 'user':
- return '"user"';
case 'old':
- return '"old"';
+ case 'group':
+ return '"' . $name . '"';
+
default:
return $name;
}
}
function strencode( $s ) {
- return pg_escape_string( $s );
+ return addslashes( $s );
}
- # Return the next in a sequence, save the value for retrieval via insertId()
+ /**
+ * Return the next in a sequence, save the value for retrieval via insertId()
+ */
function nextSequenceValue( $seqName ) {
- $value = $this->getField(""," nextval('" . $seqName . "')");
+ $value = $this->selectField(''," nextval('" . $seqName . "')");
$this->mInsertId = $value;
return $value;
}
- # USE INDEX clause
- # PostgreSQL doesn't have them and returns ""
+ /**
+ * USE INDEX clause
+ * PostgreSQL doesn't have them and returns ""
+ */
function useIndexClause( $index ) {
return '';
}
# It may be more efficient to leave off unique indexes which are unlikely to collide.
# However if you do this, you run the risk of encountering errors which wouldn't have
# occurred in MySQL
- function replace( $table, $uniqueIndexes, $rows, $fname = "Database::replace" ) {
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
$table = $this->tableName( $table );
+
+ if (count($rows)==0) {
+ return;
+ }
- # Delete rows which collide
- if ( $uniqueIndexes ) {
- $sql = "DELETE FROM $table WHERE (";
- $first = true;
- foreach ( $uniqueIndexes as $index ) {
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ") OR (";
- }
- if ( is_array( $col ) ) {
- $first2 = true;
- $sql .= "(";
- foreach ( $index as $col ) {
- if ( $first2 ) {
- $first2 = false;
- } else {
- $sql .= " AND ";
+ # Single row case
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
+
+ foreach( $rows as $row ) {
+ # Delete rows which collide
+ if ( $uniqueIndexes ) {
+ $sql = "DELETE FROM $table WHERE ";
+ $first = true;
+ foreach ( $uniqueIndexes as $index ) {
+ if ( $first ) {
+ $first = false;
+ $sql .= "(";
+ } else {
+ $sql .= ') OR (';
+ }
+ if ( is_array( $index ) ) {
+ $first2 = true;
+ foreach ( $index as $col ) {
+ if ( $first2 ) {
+ $first2 = false;
+ } else {
+ $sql .= ' AND ';
+ }
+ $sql .= $col.'=' . $this->addQuotes( $row[$col] );
}
- $sql .= "$col = " $this->strencode
-
- if ( $first ) {
- $first = false;
- } else {
- $sql .= "OR ";
+ } else {
+ $sql .= $index.'=' . $this->addQuotes( $row[$index] );
+ }
}
- $sql .= "$col IN (";
- $indexValues = array();
- foreach ( $rows as $row ) {
- $indexValues[] = $row[$col];
- }
- $sql .= $this->makeList( $indexValues, LIST_COMMA ) . ") ";
+ $sql .= ')';
+ $this->query( $sql, $fname );
}
- $this->query( $sql, $fname );
- }
- # Now insert the rows
- $sql = "INSERT INTO $table (" . $this->makeList( array_flip( $rows[0] ) ) .") VALUES ";
- $first = true;
- foreach ( $rows as $row ) {
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ",";
- }
- $sql .= "(" . $this->makeList( $row, LIST_COMMA ) . ")";
+ # Now insert the row
+ $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
+ $this->makeList( $row, LIST_COMMA ) . ')';
+ $this->query( $sql, $fname );
}
- $this->query( $sql, $fname );
}
# DELETE where the condition is a join
$joinTable = $this->tableName( $joinTable );
$sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
if ( $conds != '*' ) {
- $sql .= "WHERE " . $this->makeList( $conds, LIST_AND );
+ $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
}
- $sql .= ")";
+ $sql .= ')';
$this->query( $sql, $fname );
}
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
- $res = $this->query( "SELECT $field FROM $table LIMIT 1", "Database::textFieldLength" );
- $size = pg_field_size( $res, 0 );
+ $sql = "SELECT t.typname as ftype,a.atttypmod as size
+ FROM pg_class c, pg_attribute a, pg_type t
+ WHERE relname='$table' AND a.attrelid=c.oid AND
+ a.atttypid=t.oid and a.attname='$field'";
+ $res =$this->query($sql);
+ $row=$this->fetchObject($res);
+ if ($row->ftype=="varchar") {
+ $size=$row->size-4;
+ } else {
+ $size=$row->size;
+ }
$this->freeResult( $res );
return $size;
}
function lowPriorityOption() {
return '';
}
+
+ function limitResult($limit,$offset) {
+ return " LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
+ }
+
+ /**
+ * Returns an SQL expression for a simple conditional.
+ * Uses CASE on PostgreSQL.
+ *
+ * @param string $cond SQL expression which will result in a boolean value
+ * @param string $trueVal SQL expression to return if true
+ * @param string $falseVal SQL expression to return if false
+ * @return string SQL fragment
+ */
+ function conditional( $cond, $trueVal, $falseVal ) {
+ return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
+ }
+
+ # FIXME: actually detecting deadlocks might be nice
+ function wasDeadlock() {
+ return false;
+ }
+
+ # Return DB-style timestamp used for MySQL schema
+ function timestamp( $ts=0 ) {
+ return wfTimestamp(TS_DB,$ts);
+ }
+
+ /**
+ * Return aggregated value function call
+ */
+ function aggregateValue ($valuedata,$valuename='value') {
+ return $valuedata;
+ }
+
+
+ function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ $message = "A database error has occurred\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ wfDebugDieBacktrace($message);
+ }
+
+ /**
+ * @return string wikitext of a link to the server software's web site
+ */
+ function getSoftwareLink() {
+ return "[http://www.postgresql.org/ PostgreSQL]";
+ }
+
+ /**
+ * @return string Version information from the database
+ */
+ function getServerVersion() {
+ $res = $this->query( "SELECT version()" );
+ $row = $this->fetchRow( $res );
+ $version = $row[0];
+ $this->freeResult( $res );
+ return $version;
+ }
+
+ function setSchema($schema=false) {
+ $schemas=$this->mSchemas;
+ if ($schema) { array_unshift($schemas,$schema); }
+ $searchpath=$this->makeList($schemas,LIST_NAMES);
+ $this->query("SET search_path = $searchpath");
+ }
}
-function wfLimitResult( $limit, $offset ) {
- return " LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
+/**
+ * Just an alias.
+ * @package MediaWiki
+ */
+class DatabasePostgreSQL extends DatabasePgsql {
}
?>