2004-06-07 02:59:58 +00:00
< ? php
2004-09-02 23:28:24 +00:00
/**
2006-08-13 14:35:51 +00:00
* This is Postgres database abstraction layer .
2006-01-07 13:09:30 +00:00
*
* As it includes more generic version for DB functions ,
2004-12-03 11:44:27 +00:00
* than MySQL ones , some of them should be moved to parent
* Database class .
2004-09-02 23:28:24 +00:00
*
2004-09-03 23:00:01 +00:00
* @ package MediaWiki
2004-09-02 23:28:24 +00:00
*/
2006-06-26 23:38:50 +00:00
class DatabasePostgres extends Database {
2004-07-10 03:09:26 +00:00
var $mInsertId = NULL ;
2004-08-20 12:46:40 +00:00
var $mLastResult = NULL ;
2004-06-07 02:59:58 +00:00
2006-06-27 16:11:47 +00:00
function DatabasePostgres ( $server = false , $user = false , $password = false , $dbName = false ,
2006-06-27 00:53:46 +00:00
$failFunction = false , $flags = 0 )
2004-06-07 02:59:58 +00:00
{
2006-06-28 18:05:08 +00:00
global $wgOut , $wgDBprefix , $wgCommandLineMode ;
# Can't get a reference if it hasn't been set yet
if ( ! isset ( $wgOut ) ) {
$wgOut = NULL ;
}
$this -> mOut =& $wgOut ;
$this -> mFailFunction = $failFunction ;
2006-07-17 00:54:40 +00:00
$this -> mCascadingDeletes = true ;
$this -> mCleanupTriggers = true ;
2006-08-16 00:58:42 +00:00
$this -> mStrictIPs = true ;
2006-11-28 21:40:42 +00:00
$this -> mRealTimestamps = true ;
2006-06-28 18:05:08 +00:00
$this -> mFlags = $flags ;
$this -> open ( $server , $user , $password , $dbName );
2004-06-07 02:59:58 +00:00
}
2004-07-10 03:09:26 +00:00
2006-06-27 16:11:47 +00:00
static function newFromParams ( $server = false , $user = false , $password = false , $dbName = false ,
2006-06-27 00:53:46 +00:00
$failFunction = false , $flags = 0 )
2004-06-07 02:59:58 +00:00
{
2006-06-27 16:11:47 +00:00
return new DatabasePostgres ( $server , $user , $password , $dbName , $failFunction , $flags );
2004-06-07 02:59:58 +00:00
}
2004-07-10 03:09:26 +00:00
2004-09-02 23:28:24 +00:00
/**
* Usually aborts on failure
* If the failFunction is set to a non - zero integer , returns success
*/
2006-06-27 16:11:47 +00:00
function open ( $server , $user , $password , $dbName ) {
2006-08-13 14:35:51 +00:00
# Test for Postgres support, to avoid suppressed fatal error
2004-07-10 03:09:26 +00:00
if ( ! function_exists ( 'pg_connect' ) ) {
2006-08-17 02:48:39 +00:00
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 " );
2004-07-10 03:09:26 +00:00
}
2006-07-16 15:20:24 +00:00
2006-06-29 17:24:04 +00:00
global $wgDBport ;
2004-09-17 13:47:28 +00:00
2004-06-07 02:59:58 +00:00
$this -> close ();
$this -> mServer = $server ;
2006-06-28 18:05:08 +00:00
$port = $wgDBport ;
2004-06-07 02:59:58 +00:00
$this -> mUser = $user ;
$this -> mPassword = $password ;
$this -> mDBname = $dbName ;
2006-01-07 13:31:29 +00:00
2004-06-07 02:59:58 +00:00
$success = false ;
2006-06-28 18:05:08 +00:00
$hstring = " " ;
if ( $server != false && $server != " " ) {
$hstring = " host= $server " ;
}
if ( $port != false && $port != " " ) {
$hstring .= " port= $port " ;
}
2006-07-16 15:20:24 +00:00
if ( ! strlen ( $user )) { ## e.g. the class is being loaded
return ;
}
2006-06-28 18:05:08 +00:00
error_reporting ( E_ALL );
@ $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 " );
return false ;
}
$this -> mOpened = true ;
2006-07-16 15:20:24 +00:00
## If this is the initial connection, setup the schema stuff and possibly create the user
if ( defined ( 'MEDIAWIKI_INSTALL' )) {
2006-10-31 13:31:40 +00:00
global $wgDBname , $wgDBuser , $wgDBpassword , $wgDBsuperuser , $wgDBmwschema ,
2006-09-03 02:44:15 +00:00
$wgDBts2schema , $wgDBts2locale ;
2006-07-16 15:20:24 +00:00
print " OK</li> \n " ;
2006-09-03 23:03:53 +00:00
print " <li>Checking the version of Postgres... " ;
$version = pg_fetch_result ( $this -> doQuery ( " SELECT version() " ), 0 , 0 );
if ( ! preg_match ( " /PostgreSQL ( \ d+ \ . \ d+)( \ S+)/ " , $version , $thisver )) {
print " <b>FAILED</b> (could not determine the version)</li> \n " ;
dieout ( " </ul> " );
}
$PGMINVER = " 8.1 " ;
if ( $thisver [ 1 ] < $PGMINVER ) {
print " <b>FAILED</b>. Required version is $PGMINVER . You have $thisver[1] $thisver[2] </li> \n " ;
dieout ( " </ul> " );
}
print " version $thisver[1] $thisver[2] is OK.</li> \n " ;
2006-07-24 22:13:24 +00:00
$safeuser = $this -> quote_ident ( $wgDBuser );
2006-07-16 15:20:24 +00:00
## Are we connecting as a superuser for the first time?
if ( $wgDBsuperuser ) {
2006-07-24 22:13:24 +00:00
## Are we really a superuser? Check out our rights
$SQL = " SELECT
CASE WHEN usesuper IS TRUE THEN
CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
END AS rights
FROM pg_catalog . pg_user WHERE usename = " . $this->addQuotes ( $wgDBsuperuser );
$rows = $this -> numRows ( $res = $this -> doQuery ( $SQL ));
if ( ! $rows ) {
print " <li>ERROR: Could not read permissions for user \" $wgDBsuperuser\ " </ li > \n " ;
dieout ( '</ul>' );
}
$perms = pg_fetch_result ( $res , 0 , 0 );
2006-07-16 15:20:24 +00:00
$SQL = " SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this -> addQuotes ( $wgDBuser );
$rows = $this -> numRows ( $this -> doQuery ( $SQL ));
if ( $rows ) {
print " <li>User \" $wgDBuser\ " already exists , skipping account creation .</ li > " ;
}
else {
2006-07-24 22:13:24 +00:00
if ( $perms != 1 and $perms != 3 ) {
2006-07-16 15:20:24 +00:00
print " <li>ERROR: the user \" $wgDBsuperuser\ " cannot create other users . " ;
print 'Please use a different Postgres user.</li>' ;
dieout ( '</ul>' );
}
print " <li>Creating user <b> $wgDBuser </b>... " ;
2006-10-31 13:31:40 +00:00
$safepass = $this -> addQuotes ( $wgDBpassword );
2006-07-24 22:13:24 +00:00
$SQL = " CREATE USER $safeuser NOCREATEDB PASSWORD $safepass " ;
2006-07-16 15:20:24 +00:00
$this -> doQuery ( $SQL );
print " OK</li> \n " ;
}
## User now exists, check out the database
2006-07-24 22:13:24 +00:00
if ( $dbName != $wgDBname ) {
$SQL = " SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this -> addQuotes ( $wgDBname );
$rows = $this -> numRows ( $this -> doQuery ( $SQL ));
if ( $rows ) {
print " <li>Database \" $wgDBname\ " already exists , skipping database creation .</ li > " ;
}
else {
if ( $perms < 2 ) {
print " <li>ERROR: the user \" $wgDBsuperuser\ " cannot create databases . " ;
print 'Please use a different Postgres user.</li>' ;
dieout ( '</ul>' );
}
print " <li>Creating database <b> $wgDBname </b>... " ;
$safename = $this -> quote_ident ( $wgDBname );
$SQL = " CREATE DATABASE $safename OWNER $safeuser " ;
$this -> doQuery ( $SQL );
print " OK</li> \n " ;
## Hopefully tsearch2 and plpgsql are in template1...
}
## Reconnect to check out tsearch2 rights for this user
print " <li>Connecting to \" $wgDBname\ " as superuser \ " $wgDBsuperuser\ " to check rights ... " ;
@ $this -> mConn = pg_connect ( " $hstring dbname= $wgDBname user= $user password= $password " );
if ( $this -> mConn == false ) {
print " <b>FAILED TO CONNECT!</b></li> " ;
2006-08-13 14:35:51 +00:00
dieout ( " </ul> " );
2006-07-24 22:13:24 +00:00
}
2006-07-16 15:20:24 +00:00
print " OK</li> \n " ;
}
2006-07-24 22:13:24 +00:00
## Tsearch2 checks
2006-07-16 15:20:24 +00:00
print " <li>Checking that tsearch2 is installed in the database \" $wgDBname\ " ... " ;
if ( ! $this -> tableExists ( " pg_ts_cfg " , $wgDBts2schema )) {
print " <b>FAILED</b>. tsearch2 must be installed in the database \" $wgDBname\ " . " ;
2006-08-28 02:49:12 +00:00
print " Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a> " ;
2006-07-16 15:20:24 +00:00
print " for instructions or ask on #postgresql on irc.freenode.net</li> \n " ;
dieout ( " </ul> " );
}
print " OK</li> \n " ;
2006-07-24 22:13:24 +00:00
print " <li>Ensuring that user \" $wgDBuser\ " has select rights on the tsearch2 tables ... " ;
2006-07-16 15:20:24 +00:00
foreach ( array ( 'cfg' , 'cfgmap' , 'dict' , 'parser' ) as $table ) {
2006-07-24 22:13:24 +00:00
$SQL = " GRANT SELECT ON pg_ts_ $table TO $safeuser " ;
2006-07-16 15:20:24 +00:00
$this -> doQuery ( $SQL );
}
2006-07-24 22:13:24 +00:00
print " OK</li> \n " ;
## Setup the schema for this user if needed
$result = $this -> schemaExists ( $wgDBmwschema );
2006-08-13 14:35:51 +00:00
$safeschema = $this -> quote_ident ( $wgDBmwschema );
2006-07-24 22:13:24 +00:00
if ( ! $result ) {
print " <li>Creating schema <b> $wgDBmwschema </b> ... " ;
$result = $this -> doQuery ( " CREATE SCHEMA $safeschema AUTHORIZATION $safeuser " );
if ( ! $result ) {
print " <b>FAILED</b>.</li> \n " ;
dieout ( " </ul> " );
}
print " OK</li> \n " ;
}
2006-08-13 14:35:51 +00:00
else {
print " <li>Schema already exists, explicitly granting rights... \n " ;
$safeschema2 = $this -> addQuotes ( $wgDBmwschema );
$SQL = " SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser ;' \n " .
" FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n \n " .
" WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n " .
" AND p.relkind IN ('r','S','v') \n " ;
$SQL .= " UNION \n " ;
$SQL .= " SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('|| \n " .
" pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser ;' \n " .
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n \n " .
" WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2 " ;
$res = $this -> doQuery ( $SQL );
if ( ! $res ) {
print " <b>FAILED</b>. Could not set rights for the user.</li> \n " ;
dieout ( " </ul> " );
}
$this -> doQuery ( " SET search_path = $safeschema " );
$rows = $this -> numRows ( $res );
while ( $rows ) {
$rows -- ;
$this -> doQuery ( pg_fetch_result ( $res , $rows , 0 ));
}
print " OK</li> " ;
}
2006-07-16 15:20:24 +00:00
$wgDBsuperuser = '' ;
return true ; ## Reconnect as regular user
}
if ( ! defined ( 'POSTGRES_SEARCHPATH' )) {
2006-06-29 17:24:04 +00:00
## Do we have the basic tsearch2 table?
2006-07-16 15:20:24 +00:00
print " <li>Checking for tsearch2 in the schema \" $wgDBts2schema\ " ... " ;
2006-06-29 17:24:04 +00:00
if ( ! $this -> tableExists ( " pg_ts_dict " , $wgDBts2schema )) {
2006-07-05 03:59:54 +00:00
print " <b>FAILED</b>. Make sure tsearch2 is installed. See <a href= " ;
2006-06-29 17:24:04 +00:00
print " 'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a> " ;
print " for instructions.</li> \n " ;
dieout ( " </ul> " );
}
print " OK</li> \n " ;
2006-07-16 15:20:24 +00:00
## Does this user have the rights to the tsearch2 tables?
2006-09-03 02:44:15 +00:00
$ctype = pg_fetch_result ( $this -> doQuery ( " SHOW lc_ctype " ), 0 , 0 );
2006-07-16 15:20:24 +00:00
print " <li>Checking tsearch2 permissions... " ;
2006-09-03 02:44:15 +00:00
$SQL = " SELECT ts_name FROM $wgDBts2schema .pg_ts_cfg WHERE locale = ' $ctype ' " ;
$SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END " ;
2006-07-16 15:20:24 +00:00
error_reporting ( 0 );
$res = $this -> doQuery ( $SQL );
error_reporting ( E_ALL );
if ( ! $res ) {
print " <b>FAILED</b>. Make sure that the user \" $wgDBuser\ " has SELECT access to the tsearch2 tables </ li > \n " ;
2006-08-13 14:35:51 +00:00
dieout ( " </ul> " );
2006-07-16 15:20:24 +00:00
}
print " OK</li> " ;
2006-09-03 02:44:15 +00:00
## Will the current locale work? Can we force it to?
print " <li>Verifying tsearch2 locale with $ctype ... " ;
$rows = $this -> numRows ( $res );
$resetlocale = 0 ;
if ( ! $rows ) {
print " <b>not found</b></li> \n " ;
print " <li>Attempting to set default tsearch2 locale to \" $ctype\ " ... " ;
$resetlocale = 1 ;
}
else {
$tsname = pg_fetch_result ( $res , 0 , 0 );
if ( $tsname != 'default' ) {
print " <b>not set to default ( $tsname )</b> " ;
print " <li>Attempting to change tsearch2 default locale to \" $ctype\ " ... " ;
$resetlocale = 1 ;
}
}
if ( $resetlocale ) {
$SQL = " UPDATE $wgDBts2schema .pg_ts_cfg SET locale = ' $ctype ' WHERE ts_name = 'default' " ;
$res = $this -> doQuery ( $SQL );
if ( ! $res ) {
print " <b>FAILED</b>. " ;
print " Please make sure that the locale in pg_ts_cfg for \" default \" is set to \" ctype \" </li> \n " ;
dieout ( " </ul> " );
}
print " OK</li> " ;
}
## Final test: try out a simple tsearch2 query
$SQL = " SELECT $wgDBts2schema .to_tsvector('default','MediaWiki tsearch2 testing') " ;
$res = $this -> doQuery ( $SQL );
if ( ! $res ) {
print " <b>FAILED</b>. Specifically, \" $SQL\ " did not work .</ li > " ;
dieout ( " </ul> " );
}
print " OK</li> " ;
2006-07-05 03:59:54 +00:00
## Do we have plpgsql installed?
2006-07-24 22:13:24 +00:00
print " <li>Checking for Pl/Pgsql ... " ;
2006-07-05 03:59:54 +00:00
$SQL = " SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql' " ;
$rows = $this -> numRows ( $this -> doQuery ( $SQL ));
if ( $rows < 1 ) {
2006-08-19 12:43:13 +00:00
// plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
print " not installed. Attempting to install Pl/Pgsql ... " ;
$SQL = " SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) " .
" WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog' " ;
$rows = $this -> numRows ( $this -> doQuery ( $SQL ));
if ( $rows >= 1 ) {
$result = $this -> doQuery ( " CREATE LANGUAGE plpgsql " );
if ( ! $result ) {
print " <b>FAILED</b>. You need to install the language plpgsql in the database <tt> $wgDBname </tt></li> " ;
dieout ( " </ul> " );
}
}
else {
print " <b>FAILED</b>. You need to install the language plpgsql in the database <tt> $wgDBname </tt></li> " ;
dieout ( " </ul> " );
}
2006-07-05 03:59:54 +00:00
}
print " OK</li> \n " ;
2006-06-28 18:05:08 +00:00
## Does the schema already exist? Who owns it?
2006-06-29 17:24:04 +00:00
$result = $this -> schemaExists ( $wgDBmwschema );
2006-06-28 18:05:08 +00:00
if ( ! $result ) {
2006-06-29 17:24:04 +00:00
print " <li>Creating schema <b> $wgDBmwschema </b> ... " ;
$result = $this -> doQuery ( " CREATE SCHEMA $wgDBmwschema " );
2006-06-28 18:05:08 +00:00
if ( ! $result ) {
2006-07-16 15:20:24 +00:00
print " <b>FAILED</b>.</li> \n " ;
2006-07-24 22:13:24 +00:00
dieout ( " </ul> " );
2006-06-28 18:05:08 +00:00
}
2006-07-24 22:13:24 +00:00
print " OK</li> \n " ;
2006-06-28 18:05:08 +00:00
}
else if ( $result != $user ) {
2006-07-16 15:20:24 +00:00
print " <li>Schema \" $wgDBmwschema\ " exists but is not owned by \ " $user\ " . Not ideal .</ li > \n " ;
2006-06-28 18:05:08 +00:00
}
else {
2006-07-16 15:20:24 +00:00
print " <li>Schema \" $wgDBmwschema\ " exists and is owned by \ " $user\ " . Excellent .</ li > \n " ;
2004-09-06 06:57:32 +00:00
}
2006-06-27 00:53:46 +00:00
2006-06-28 18:05:08 +00:00
## Fix up the search paths if needed
2006-07-16 15:20:24 +00:00
print " <li>Setting the search path for user \" $user\ " ... " ;
2006-07-24 22:13:24 +00:00
$path = $this -> quote_ident ( $wgDBmwschema );
2006-06-29 17:24:04 +00:00
if ( $wgDBts2schema !== $wgDBmwschema )
2006-07-24 22:13:24 +00:00
$path .= " , " . $this -> quote_ident ( $wgDBts2schema );
2006-06-29 17:24:04 +00:00
if ( $wgDBmwschema !== 'public' and $wgDBts2schema !== 'public' )
$path .= " , public " ;
2006-07-24 22:13:24 +00:00
$SQL = " ALTER USER $safeuser SET search_path = $path " ;
2006-06-28 18:05:08 +00:00
$result = pg_query ( $this -> mConn , $SQL );
if ( ! $result ) {
2006-07-16 15:20:24 +00:00
print " <b>FAILED</b>.</li> \n " ;
2006-07-24 22:13:24 +00:00
dieout ( " </ul> " );
2006-06-28 18:05:08 +00:00
}
2006-07-24 22:13:24 +00:00
print " OK</li> \n " ;
2006-06-28 18:05:08 +00:00
## Set for the rest of this session
2006-06-29 17:24:04 +00:00
$SQL = " SET search_path = $path " ;
2006-06-28 18:05:08 +00:00
$result = pg_query ( $this -> mConn , $SQL );
if ( ! $result ) {
print " <li>Failed to set search_path</li> \n " ;
2006-07-24 22:13:24 +00:00
dieout ( " </ul> " );
2004-06-07 02:59:58 +00:00
}
2006-06-29 17:24:04 +00:00
define ( " POSTGRES_SEARCHPATH " , $path );
2006-07-16 15:20:24 +00:00
}}
2006-06-28 18:05:08 +00:00
2006-08-18 16:24:48 +00:00
global $wgCommandLineMode ;
## If called from the command-line (e.g. importDump), only show errors
if ( $wgCommandLineMode ) {
$this -> doQuery ( " SET client_min_messages = 'ERROR' " );
}
2004-06-07 02:59:58 +00:00
return $this -> mConn ;
}
2006-01-07 13:31:29 +00:00
2004-09-02 23:28:24 +00:00
/**
* Closes a database connection , if it is open
* Returns success , true if already closed
*/
function close () {
2004-06-07 02:59:58 +00:00
$this -> mOpened = false ;
if ( $this -> mConn ) {
return pg_close ( $this -> mConn );
} else {
return true ;
}
}
2006-01-07 13:31:29 +00:00
2004-07-24 07:24:04 +00:00
function doQuery ( $sql ) {
2004-08-20 12:46:40 +00:00
return $this -> mLastResult = pg_query ( $this -> mConn , $sql );
2004-06-07 02:59:58 +00:00
}
2006-01-07 13:31:29 +00:00
2004-08-22 17:24:50 +00:00
function queryIgnore ( $sql , $fname = '' ) {
2004-07-10 03:09:26 +00:00
return $this -> query ( $sql , $fname , true );
}
2006-01-07 13:31:29 +00:00
2004-06-07 02:59:58 +00:00
function freeResult ( $res ) {
if ( !@ pg_free_result ( $res ) ) {
2006-08-13 14:35:51 +00:00
throw new DBUnexpectedError ( $this , " Unable to free Postgres result \n " );
2004-06-07 02:59:58 +00:00
}
}
2006-01-07 13:31:29 +00:00
2004-06-07 02:59:58 +00:00
function fetchObject ( $res ) {
@ $row = pg_fetch_object ( $res );
# FIXME: HACK HACK HACK HACK debug
2006-01-07 13:31:29 +00:00
2004-06-07 02:59:58 +00:00
# TODO:
# hashar : not sure if the following test really trigger if the object
# fetching failled.
2004-06-10 06:37:12 +00:00
if ( pg_last_error ( $this -> mConn ) ) {
2006-06-06 23:07:26 +00:00
throw new DBUnexpectedError ( $this , 'SQL error: ' . htmlspecialchars ( pg_last_error ( $this -> mConn ) ) );
2004-06-07 02:59:58 +00:00
}
return $row ;
}
2004-06-10 13:02:27 +00:00
function fetchRow ( $res ) {
@ $row = pg_fetch_array ( $res );
2006-03-06 04:20:20 +00:00
if ( pg_last_error ( $this -> mConn ) ) {
2006-06-06 23:07:26 +00:00
throw new DBUnexpectedError ( $this , 'SQL error: ' . htmlspecialchars ( pg_last_error ( $this -> mConn ) ) );
2006-03-07 01:10:39 +00:00
}
2004-06-10 13:02:27 +00:00
return $row ;
}
2004-06-07 02:59:58 +00:00
function numRows ( $res ) {
2006-01-07 13:09:30 +00:00
@ $n = pg_num_rows ( $res );
2004-06-10 06:37:12 +00:00
if ( pg_last_error ( $this -> mConn ) ) {
2006-06-06 23:07:26 +00:00
throw new DBUnexpectedError ( $this , 'SQL error: ' . htmlspecialchars ( pg_last_error ( $this -> mConn ) ) );
2004-06-07 02:59:58 +00:00
}
return $n ;
}
function numFields ( $res ) { return pg_num_fields ( $res ); }
function fieldName ( $res , $n ) { return pg_field_name ( $res , $n ); }
2006-01-07 13:31:29 +00:00
2004-09-02 23:28:24 +00:00
/**
* This must be called after nextSequenceVal
*/
2006-01-07 13:09:30 +00:00
function insertId () {
2004-07-10 03:09:26 +00:00
return $this -> mInsertId ;
2004-06-07 02:59:58 +00:00
}
2004-07-10 03:09:26 +00:00
2004-06-07 02:59:58 +00:00
function dataSeek ( $res , $row ) { return pg_result_seek ( $res , $row ); }
2006-06-27 00:53:46 +00:00
function lastError () {
if ( $this -> mConn ) {
return pg_last_error ();
}
else {
return " No database connection " ;
}
}
2006-07-21 19:34:45 +00:00
function lastErrno () {
return pg_last_error () ? 1 : 0 ;
}
2004-07-24 07:24:04 +00:00
2006-01-07 13:09:30 +00:00
function affectedRows () {
return pg_affected_rows ( $this -> mLastResult );
2004-06-11 14:32:05 +00:00
}
2006-01-07 13:31:29 +00:00
2004-09-02 23:28:24 +00:00
/**
* Returns information about an index
* If errors are explicitly ignored , returns NULL on failure
*/
function indexInfo ( $table , $index , $fname = 'Database::indexExists' ) {
2004-06-09 16:13:46 +00:00
$sql = " SELECT indexname FROM pg_indexes WHERE tablename=' $table ' " ;
2004-07-10 03:09:26 +00:00
$res = $this -> query ( $sql , $fname );
2004-06-07 02:59:58 +00:00
if ( ! $res ) {
return NULL ;
}
2006-01-07 13:31:29 +00:00
2004-06-07 02:59:58 +00:00
while ( $row = $this -> fetchObject ( $res ) ) {
2006-06-28 18:05:08 +00:00
if ( $row -> indexname == $index ) {
2004-07-10 03:09:26 +00:00
return $row ;
2006-11-11 21:42:46 +00:00
// BUG: !!!! This code needs to be synced up with database.php
2004-06-07 02:59:58 +00:00
}
}
2004-07-10 03:09:26 +00:00
return false ;
2004-06-07 02:59:58 +00:00
}
2004-09-09 07:12:11 +00:00
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 ;
2006-01-07 13:09:30 +00:00
while ( $row = $this -> fetchObject ( $res ))
2004-09-09 07:12:11 +00:00
return true ;
return false ;
2006-01-07 13:31:29 +00:00
2004-09-09 07:12:11 +00:00
}
2004-10-24 07:10:33 +00:00
function insert ( $table , $a , $fname = 'Database::insert' , $options = array () ) {
2006-08-13 14:35:51 +00:00
# Postgres doesn't support options
2004-07-18 08:48:43 +00:00
# We have a go at faking one of them
2006-01-07 13:09:30 +00:00
# TODO: DELAYED, LOW_PRIORITY
2004-07-18 08:48:43 +00:00
2004-09-01 12:27:57 +00:00
if ( ! is_array ( $options ))
$options = array ( $options );
2006-01-07 13:09:30 +00:00
if ( in_array ( 'IGNORE' , $options ) )
2004-07-24 07:24:04 +00:00
$oldIgnore = $this -> ignoreErrors ( true );
2004-09-01 12:27:57 +00:00
# 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 );
}
foreach ( $a as $row ) {
2004-10-24 07:10:33 +00:00
parent :: insert ( $table , $row , $fname , array () );
2004-06-07 02:59:58 +00:00
}
2004-09-01 12:27:57 +00:00
$this -> ignoreErrors ( $oldIgnore );
$retVal = true ;
2006-01-07 13:09:30 +00:00
if ( in_array ( 'IGNORE' , $options ) )
2004-09-01 12:27:57 +00:00
$this -> ignoreErrors ( $oldIgnore );
2004-07-10 03:09:26 +00:00
return $retVal ;
2004-06-07 02:59:58 +00:00
}
2006-01-07 13:31:29 +00:00
2004-07-10 03:09:26 +00:00
function tableName ( $name ) {
2006-07-23 02:04:40 +00:00
# Replace reserved words with better ones
2004-07-10 03:09:26 +00:00
switch ( $name ) {
case 'user' :
2006-07-23 02:04:40 +00:00
return 'mwuser' ;
case 'text' :
return 'pagecontent' ;
2004-07-10 03:09:26 +00:00
default :
return $name ;
}
2004-06-07 02:59:58 +00:00
}
2004-09-02 23:28:24 +00:00
/**
* Return the next in a sequence , save the value for retrieval via insertId ()
*/
2004-07-10 03:09:26 +00:00
function nextSequenceValue ( $seqName ) {
2006-07-05 03:59:54 +00:00
$safeseq = preg_replace ( " /'/ " , " '' " , $seqName );
$res = $this -> query ( " SELECT nextval(' $safeseq ') " );
$row = $this -> fetchRow ( $res );
$this -> mInsertId = $row [ 0 ];
$this -> freeResult ( $res );
return $this -> mInsertId ;
2004-07-10 03:09:26 +00:00
}
2004-06-07 02:59:58 +00:00
2004-09-02 23:28:24 +00:00
/**
2006-08-13 14:35:51 +00:00
* Postgres does not have a " USE INDEX " clause , so return an empty string
2004-09-02 23:28:24 +00:00
*/
2004-07-10 03:09:26 +00:00
function useIndexClause ( $index ) {
return '' ;
}
2004-06-07 02:59:58 +00:00
2004-07-10 03:09:26 +00:00
# REPLACE query wrapper
2006-08-13 14:35:51 +00:00
# Postgres simulates this with a DELETE followed by INSERT
2004-07-10 03:09:26 +00:00
# $row is the row to insert, an associative array
2006-01-07 13:09:30 +00:00
# $uniqueIndexes is an array of indexes. Each element may be either a
2004-07-10 03:09:26 +00:00
# field name or an array of field names
#
2006-01-07 13:09:30 +00:00
# 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
2004-07-10 03:09:26 +00:00
# occurred in MySQL
2004-08-22 17:24:50 +00:00
function replace ( $table , $uniqueIndexes , $rows , $fname = 'Database::replace' ) {
2004-07-10 03:09:26 +00:00
$table = $this -> tableName ( $table );
2006-01-07 13:31:29 +00:00
2004-09-06 09:56:29 +00:00
if ( count ( $rows ) == 0 ) {
return ;
}
2004-07-10 03:09:26 +00:00
2004-07-18 08:48:43 +00:00
# Single row case
if ( ! is_array ( reset ( $rows ) ) ) {
$rows = array ( $rows );
}
foreach ( $rows as $row ) {
# Delete rows which collide
if ( $uniqueIndexes ) {
2004-09-06 08:30:42 +00:00
$sql = " DELETE FROM $table WHERE " ;
2004-07-18 08:48:43 +00:00
$first = true ;
foreach ( $uniqueIndexes as $index ) {
if ( $first ) {
$first = false ;
2004-09-07 08:37:50 +00:00
$sql .= " ( " ;
2004-07-18 08:48:43 +00:00
} else {
2004-08-22 17:24:50 +00:00
$sql .= ') OR (' ;
2004-07-15 15:09:32 +00:00
}
2004-07-18 08:48:43 +00:00
if ( is_array ( $index ) ) {
$first2 = true ;
foreach ( $index as $col ) {
2006-01-07 13:09:30 +00:00
if ( $first2 ) {
2004-07-18 08:48:43 +00:00
$first2 = false ;
} else {
2004-08-22 17:24:50 +00:00
$sql .= ' AND ' ;
2004-07-18 08:48:43 +00:00
}
2004-08-22 17:24:50 +00:00
$sql .= $col . '=' . $this -> addQuotes ( $row [ $col ] );
2004-07-18 08:48:43 +00:00
}
2004-09-07 08:37:50 +00:00
} else {
2004-08-22 17:24:50 +00:00
$sql .= $index . '=' . $this -> addQuotes ( $row [ $index ] );
2004-09-07 08:37:50 +00:00
}
2004-07-10 03:09:26 +00:00
}
2004-08-22 17:24:50 +00:00
$sql .= ')' ;
2004-07-18 08:48:43 +00:00
$this -> query ( $sql , $fname );
2004-07-10 03:09:26 +00:00
}
2004-07-18 08:48:43 +00:00
# Now insert the row
2004-09-07 08:37:50 +00:00
$sql = " INSERT INTO $table ( " . $this -> makeList ( array_keys ( $row ), LIST_NAMES ) . ') VALUES (' .
2004-07-18 08:48:43 +00:00
$this -> makeList ( $row , LIST_COMMA ) . ')' ;
$this -> query ( $sql , $fname );
2004-07-10 03:09:26 +00:00
}
}
# DELETE where the condition is a join
function deleteJoin ( $delTable , $joinTable , $delVar , $joinVar , $conds , $fname = " Database::deleteJoin " ) {
if ( ! $conds ) {
2006-06-06 23:07:26 +00:00
throw new DBUnexpectedError ( $this , 'Database::deleteJoin() called with empty $conds' );
2004-06-07 02:59:58 +00:00
}
2004-07-10 03:09:26 +00:00
$delTable = $this -> tableName ( $delTable );
$joinTable = $this -> tableName ( $joinTable );
$sql = " DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable " ;
if ( $conds != '*' ) {
2004-08-22 17:24:50 +00:00
$sql .= 'WHERE ' . $this -> makeList ( $conds , LIST_AND );
2004-06-07 02:59:58 +00:00
}
2004-08-22 17:24:50 +00:00
$sql .= ')' ;
2004-07-10 03:09:26 +00:00
$this -> query ( $sql , $fname );
2004-06-07 02:59:58 +00:00
}
2004-07-10 03:09:26 +00:00
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize ( $table , $field ) {
$table = $this -> tableName ( $table );
2004-09-11 14:08:52 +00:00
$sql = " SELECT t.typname as ftype,a.atttypmod as size
2006-01-07 13:09:30 +00:00
FROM pg_class c , pg_attribute a , pg_type t
WHERE relname = '$table' AND a . attrelid = c . oid AND
2004-09-11 14:08:52 +00:00
a . atttypid = t . oid and a . attname = '$field' " ;
$res = $this -> query ( $sql );
$row = $this -> fetchObject ( $res );
if ( $row -> ftype == " varchar " ) {
2006-01-07 13:31:29 +00:00
$size = $row -> size - 4 ;
2004-09-11 14:08:52 +00:00
} else {
$size = $row -> size ;
}
2004-07-10 03:09:26 +00:00
$this -> freeResult ( $res );
return $size ;
}
2006-01-07 13:31:29 +00:00
2004-07-10 03:09:26 +00:00
function lowPriorityOption () {
return '' ;
}
2004-07-15 15:09:32 +00:00
2005-08-09 10:30:20 +00:00
function limitResult ( $sql , $limit , $offset ) {
2006-03-07 01:10:39 +00:00
return " $sql LIMIT $limit " . ( is_numeric ( $offset ) ? " OFFSET { $offset } " : " " );
2004-07-15 15:09:32 +00:00
}
2006-01-07 13:31:29 +00:00
2004-09-09 00:02:38 +00:00
/**
* Returns an SQL expression for a simple conditional .
2006-08-13 14:35:51 +00:00
* Uses CASE on Postgres
2004-09-09 00:02:38 +00:00
*
* @ 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) " ;
}
2004-07-18 08:48:43 +00:00
# FIXME: actually detecting deadlocks might be nice
function wasDeadlock () {
return false ;
}
2004-08-10 11:12:18 +00:00
2004-09-08 20:36:41 +00:00
function timestamp ( $ts = 0 ) {
2006-07-18 01:40:38 +00:00
return wfTimestamp ( TS_POSTGRES , $ts );
2004-09-08 20:36:41 +00:00
}
2006-03-07 01:10:39 +00:00
/**
* Return aggregated value function call
*/
function aggregateValue ( $valuedata , $valuename = 'value' ) {
return $valuedata ;
}
2004-09-09 12:04:39 +00:00
2004-09-08 20:36:41 +00:00
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 " ;
2006-06-06 23:07:26 +00:00
throw new DBUnexpectedError ( $this , $message );
2004-08-19 13:00:34 +00:00
}
2004-09-08 20:36:41 +00:00
/**
* @ return string wikitext of a link to the server software ' s web site
*/
function getSoftwareLink () {
return " [http://www.postgresql.org/ PostgreSQL] " ;
}
2006-01-07 13:31:29 +00:00
2004-09-08 20:36:41 +00:00
/**
* @ 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 ;
}
2004-09-30 18:56:10 +00:00
2006-06-27 17:06:36 +00:00
/**
2006-06-29 17:24:04 +00:00
* Query whether a given table exists ( in the given schema , or the default mw one if not given )
2006-06-27 17:06:36 +00:00
*/
2006-06-29 17:24:04 +00:00
function tableExists ( $table , $schema = false ) {
global $wgDBmwschema ;
if ( ! $schema )
$schema = $wgDBmwschema ;
$etable = preg_replace ( " /'/ " , " '' " , $table );
$eschema = preg_replace ( " /'/ " , " '' " , $schema );
2006-06-27 17:06:36 +00:00
$SQL = " SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
2006-08-28 02:49:12 +00:00
. " WHERE c.relnamespace = n.oid AND c.relname = ' $etable ' AND n.nspname = ' $eschema ' "
. " AND c.relkind IN ('r','v') " ;
2006-06-29 17:24:04 +00:00
$res = $this -> query ( $SQL );
2006-06-28 18:05:08 +00:00
$count = $res ? pg_num_rows ( $res ) : 0 ;
if ( $res )
2006-06-27 17:06:36 +00:00
$this -> freeResult ( $res );
2006-06-28 18:05:08 +00:00
return $count ;
}
2006-06-29 17:24:04 +00:00
2006-06-28 18:05:08 +00:00
/**
* Query whether a given schema exists . Returns the name of the owner
*/
2006-06-29 17:24:04 +00:00
function schemaExists ( $schema ) {
$eschema = preg_replace ( " /'/ " , " '' " , $schema );
2006-06-28 18:05:08 +00:00
$SQL = " SELECT rolname FROM pg_catalog.pg_namespace n, pg_catalog.pg_roles r "
2006-06-29 17:24:04 +00:00
. " WHERE n.nspowner=r.oid AND n.nspname = ' $eschema ' " ;
$res = $this -> query ( $SQL );
2006-06-28 18:05:08 +00:00
$owner = $res ? pg_num_rows ( $res ) ? pg_fetch_result ( $res , 0 , 0 ) : false : false ;
if ( $res )
$this -> freeResult ( $res );
return $owner ;
2006-06-27 17:06:36 +00:00
}
/**
2006-06-29 17:24:04 +00:00
* Query whether a given column exists in the mediawiki schema
2006-06-27 17:06:36 +00:00
*/
2006-06-29 17:24:04 +00:00
function fieldExists ( $table , $field ) {
global $wgDBmwschema ;
$etable = preg_replace ( " /'/ " , " '' " , $table );
$eschema = preg_replace ( " /'/ " , " '' " , $wgDBmwschema );
$ecol = preg_replace ( " /'/ " , " '' " , $field );
2006-06-27 17:06:36 +00:00
$SQL = " SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a "
2006-06-29 17:24:04 +00:00
. " WHERE c.relnamespace = n.oid AND c.relname = ' $etable ' AND n.nspname = ' $eschema ' "
. " AND a.attrelid = c.oid AND a.attname = ' $ecol ' " ;
$res = $this -> query ( $SQL );
2006-06-28 18:05:08 +00:00
$count = $res ? pg_num_rows ( $res ) : 0 ;
if ( $res )
2006-06-27 17:06:36 +00:00
$this -> freeResult ( $res );
2006-06-28 18:05:08 +00:00
return $count ;
}
function fieldInfo ( $table , $field ) {
$res = $this -> query ( " SELECT $field FROM $table LIMIT 1 " );
$type = pg_field_type ( $res , 0 );
return $type ;
2006-06-27 17:06:36 +00:00
}
2006-07-05 04:06:29 +00:00
function begin ( $fname = 'DatabasePostgrs::begin' ) {
$this -> query ( 'BEGIN' , $fname );
$this -> mTrxLevel = 1 ;
}
function immediateCommit ( $fname = 'DatabasePostgres::immediateCommit' ) {
return true ;
}
function commit ( $fname = 'DatabasePostgres::commit' ) {
2006-06-29 01:55:52 +00:00
$this -> query ( 'COMMIT' , $fname );
$this -> mTrxLevel = 0 ;
}
2006-06-29 17:24:04 +00:00
/* Not even sure why this is used in the main codebase... */
2006-06-29 01:55:52 +00:00
function limitResultForUpdate ( $sql , $num ) {
return $sql ;
}
2006-07-16 12:28:38 +00:00
function setup_database () {
global $wgVersion , $wgDBmwschema , $wgDBts2schema , $wgDBport ;
dbsource ( " ../maintenance/postgres/tables.sql " , $this );
## Update version information
$mwv = $this -> addQuotes ( $wgVersion );
$pgv = $this -> addQuotes ( $this -> getServerVersion ());
$pgu = $this -> addQuotes ( $this -> mUser );
$mws = $this -> addQuotes ( $wgDBmwschema );
$tss = $this -> addQuotes ( $wgDBts2schema );
$pgp = $this -> addQuotes ( $wgDBport );
$dbn = $this -> addQuotes ( $this -> mDBname );
2006-09-03 23:03:53 +00:00
$ctype = pg_fetch_result ( $this -> doQuery ( " SHOW lc_ctype " ), 0 , 0 );
2006-07-16 12:28:38 +00:00
$SQL = " UPDATE mediawiki_version SET mw_version= $mwv , pg_version= $pgv , pg_user= $pgu , " .
2006-09-05 01:56:09 +00:00
" mw_schema = $mws , ts2_schema = $tss , pg_port= $pgp , pg_dbname= $dbn , " .
2006-09-03 23:03:53 +00:00
" ctype = ' $ctype ' " .
2006-07-16 12:28:38 +00:00
" WHERE type = 'Creation' " ;
$this -> query ( $SQL );
2006-06-29 01:55:52 +00:00
## Avoid the non-standard "REPLACE INTO" syntax
$f = fopen ( " ../maintenance/interwiki.sql " , 'r' );
if ( $f == false ) {
dieout ( " <li>Could not find the interwiki.sql file " );
}
## We simply assume it is already empty as we have just created it
$SQL = " INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES " ;
while ( ! feof ( $f ) ) {
$line = fgets ( $f , 1024 );
if ( ! preg_match ( " /^ \ s*( \ (.+?),( \ d) \ )/ " , $line , $matches )) {
continue ;
}
$yesno = $matches [ 2 ]; ## ? "'true'" : "'false'";
$this -> query ( " $SQL $matches[1] , $matches[2] ) " );
}
print " (table interwiki successfully populated)... \n " ;
}
2006-07-05 03:59:54 +00:00
function encodeBlob ( $b ) {
return array ( 'bytea' , pg_escape_bytea ( $b ));
}
function decodeBlob ( $b ) {
return pg_unescape_bytea ( $b );
}
function strencode ( $s ) { ## Should not be called by us
return pg_escape_string ( $s );
}
function addQuotes ( $s ) {
if ( is_null ( $s ) ) {
return 'NULL' ;
} else if ( is_array ( $s )) { ## Assume it is bytea data
return " E' $s[1] ' " ;
}
return " ' " . pg_escape_string ( $s ) . " ' " ;
return " E' " . pg_escape_string ( $s ) . " ' " ;
}
2006-07-24 22:13:24 +00:00
function quote_ident ( $s ) {
return '"' . preg_replace ( '/"/' , '""' , $s ) . '"' ;
}
2004-06-07 02:59:58 +00:00
}
?>