00001 <?php
00002
# $Id: DatabasePostgreSQL.php,v 1.7 2004/06/12 06:05:02 timstarling Exp $
00003
#
00004
# DO NOT USE !!! Unless you want to help developping it.
00005
#
00006
# This file is an attempt to port the mysql database layer to postgreSQL. The
00007
# only thing done so far is s/mysql/pg/ and dieing if function haven't been
00008
# ported.
00009
#
00010
# As said brion 07/06/2004 :
00011
# "table definitions need to be changed. fulltext index needs to work differently
00012
# things that use the last insert id need to be changed. Probably other things
00013
# need to be changed. various semantics may be different."
00014
#
00015
# Hashar
00016
00017 require_once(
"FulltextStoplist.php" );
00018 require_once(
"CacheManager.php" );
00019
00020 define(
"DB_READ", -1 );
00021 define(
"DB_WRITE", -2 );
00022 define(
"DB_LAST", -3 );
00023
00024 define(
"LIST_COMMA", 0 );
00025 define(
"LIST_AND", 1 );
00026 define(
"LIST_SET", 2 );
00027
00028
class Database {
00029
00030
#------------------------------------------------------------------------------
00031
# Variables
00032
#------------------------------------------------------------------------------
00033 var
$mLastQuery =
"";
00034 var
$mBufferResults =
true;
00035 var
$mIgnoreErrors =
false;
00036
00037 var
$mServer,
$mUser,
$mPassword,
$mConn,
$mDBname;
00038 var
$mOut,
$mDebug,
$mOpened =
false;
00039
00040 var
$mFailFunction;
00041 var
$mLastResult;
00042
00043
#------------------------------------------------------------------------------
00044
# Accessors
00045
#------------------------------------------------------------------------------
00046
# Set functions
00047
# These set a variable and return the previous state
00048
00049
# Fail function, takes a Database as a parameter
00050
# Set to false for default, 1 for ignore errors
00051 function
setFailFunction( $function ) {
return wfSetVar( $this->mFailFunction, $function ); }
00052
00053
# Output page, used for reporting errors
00054
# FALSE means discard output
00055 function &
setOutputPage( &$out ) { $this->mOut =& $out; }
00056
00057
# Boolean, controls output of large amounts of debug information
00058 function
setDebug( $debug ) {
return wfSetVar( $this->mDebug, $debug ); }
00059
00060
# Turns buffering of SQL result sets on (true) or off (false). Default is
00061
# "on" and it should not be changed without good reasons.
00062 function
setBufferResults( $buffer ) {
return wfSetVar( $this->mBufferResults, $buffer ); }
00063
00064
# Turns on (false) or off (true) the automatic generation and sending
00065
# of a "we're sorry, but there has been a database error" page on
00066
# database errors. Default is on (false). When turned off, the
00067
# code should use wfLastErrno() and wfLastError() to handle the
00068
# situation as appropriate.
00069 function
setIgnoreErrors( $ignoreErrors ) {
return wfSetVar( $this->mIgnoreErrors, $ignoreErrors ); }
00070
00071
# Get functions
00072
00073 function
lastQuery() {
return $this->mLastQuery; }
00074 function
isOpen() {
return $this->mOpened; }
00075
00076
#------------------------------------------------------------------------------
00077
# Other functions
00078
#------------------------------------------------------------------------------
00079
00080 function
Database()
00081 {
00082 global
$wgOut;
00083
# Can't get a reference if it hasn't been set yet
00084
if ( !isset( $wgOut ) ) {
00085
$wgOut = NULL;
00086 }
00087 $this->mOut =&
$wgOut;
00088
00089 }
00090
00091 function
newFromParams( $server, $user, $password, $dbName,
00092 $failFunction =
false, $debug =
false, $bufferResults =
true, $ignoreErrors =
false )
00093 {
00094 $db =
new Database;
00095 $db->mFailFunction = $failFunction;
00096 $db->mIgnoreErrors = $ignoreErrors;
00097 $db->mDebug = $debug;
00098 $db->mBufferResults = $bufferResults;
00099 $db->open( $server, $
user, $
password, $dbName );
00100
return $db;
00101 }
00102
00103
# Usually aborts on failure
00104
# If the failFunction is set to a non-zero integer, returns success
00105 function
open( $server, $user, $password, $dbName )
00106 {
00107 global
$wgEmergencyContact;
00108
00109 $this->
close();
00110 $this->mServer = $server;
00111 $this->mUser =
$user;
00112 $this->mPassword = $password;
00113 $this->mDBname = $dbName;
00114
00115 $success =
false;
00116
00117
00118
if (
"" != $dbName ) {
00119
# start a database connection
00120
@$this->mConn = pg_connect(
"host=$server dbname=$dbName user=$user password=$password");
00121
if ( $this->mConn ==
false ) {
00122
wfDebug(
"DB connection error\n" );
00123
wfDebug(
"Server: $server, Database: $dbName, User: $user, Password: " . substr( $
password, 0, 3 ) .
"...\n" );
00124
wfDebug( $this->lastError().
"\n" );
00125 }
00126 }
00127
return $this->mConn;
00128 }
00129
00130
# Closes a database connection, if it is open
00131
# Returns success, true if already closed
00132 function
close()
00133 {
00134 $this->mOpened =
false;
00135
if ( $this->mConn ) {
00136
return pg_close( $this->mConn );
00137 }
else {
00138
return true;
00139 }
00140 }
00141
00142 function
reportConnectionError( $msg =
"")
00143 {
00144
if ( $this->mFailFunction ) {
00145
if ( !is_int( $this->mFailFunction ) ) {
00146 $this->
$mFailFunction( $
this );
00147 }
00148 }
else {
00149
wfEmergencyAbort( $
this );
00150 }
00151 }
00152
00153
# Usually aborts on failure
00154
# If errors are explicitly ignored, returns success
00155 function
query( $sql, $fname =
"" )
00156 {
00157 global
$wgProfiling;
00158
00159
if (
$wgProfiling ) {
00160
# generalizeSQL will probably cut down the query to reasonable
00161
# logging size most of the time. The substr is really just a sanity check.
00162
$profName =
"query: " . substr(
Database::generalizeSQL( $sql ), 0, 255 );
00163
wfProfileIn( $profName );
00164 }
00165
00166 $this->mLastQuery =
$sql;
00167
00168
if ( $this->mDebug ) {
00169 $sqlx = substr( $sql, 0, 500 );
00170 $sqlx = wordwrap(strtr($sqlx,
"\t\n",
" "));
00171
wfDebug(
"SQL: $sqlx\n" );
00172 }
00173
00174 $ret = pg_query( $this->mConn , $sql);
00175 $this->mLastResult = $ret;
00176
if (
false == $ret ) {
00177 $error = pg_last_error( $this->mConn );
00178
00179
00180
if( $this->
mIgnoreErrors ) {
00181
wfDebug(
"SQL ERROR (ignored): " . $error .
"\n");
00182 }
else {
00183
wfDebug(
"SQL ERROR: " . $error .
"\n");
00184
if ( $this->mOut ) {
00185
00186 $this->mOut->databaseError( $fname, $sql, $error, 0 );
00187 }
00188 }
00189 }
00190
00191
if (
$wgProfiling ) {
00192
wfProfileOut( $profName );
00193 }
00194
return $ret;
00195 }
00196
00197 function
freeResult( $res ) {
00198
if ( !@pg_free_result( $res ) ) {
00199
wfDebugDieBacktrace(
"Unable to free PostgreSQL result\n" );
00200 }
00201 }
00202 function
fetchObject( $res ) {
00203 @
$row = pg_fetch_object( $res );
00204
# FIXME: HACK HACK HACK HACK debug
00205
00206
# TODO:
00207
# hashar : not sure if the following test really trigger if the object
00208
# fetching failled.
00209
if( pg_last_error($this->mConn) ) {
00210
wfDebugDieBacktrace(
"SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
00211 }
00212
return $row;
00213 }
00214
00215 function
fetchRow( $res ) {
00216 @
$row = pg_fetch_array( $res );
00217
if( pg_last_error($this->mConn) ) {
00218
wfDebugDieBacktrace(
"SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
00219 }
00220
return $row;
00221 }
00222
00223 function
numRows( $res ) {
00224 @
$n = pg_num_rows( $res );
00225
if( pg_last_error($this->mConn) ) {
00226
wfDebugDieBacktrace(
"SQL error: " . htmlspecialchars( pg_last_error($this->mConn) ) );
00227 }
00228
return $n;
00229 }
00230 function
numFields( $res ) {
return pg_num_fields( $res ); }
00231 function
fieldName( $res, $n ) {
return pg_field_name( $res, $
n ); }
00232
00233 function
insertId() {
00234
00235
wfDebugDieBacktrace(
"Database::insertId() error : not implemented for postgre, use sequences" );
00236 }
00237 function
dataSeek( $res, $row ) {
return pg_result_seek( $res, $row ); }
00238 function
lastErrno() {
return $this->
lastError(); }
00239 function
lastError() {
return pg_last_error(); }
00240 function
affectedRows() {
00241
return pg_affected_rows( $this->mLastResult );
00242 }
00243
00244
# Simple UPDATE wrapper
00245
# Usually aborts on failure
00246
# If errors are explicitly ignored, returns success
00247 function
set( $table, $var, $value, $cond, $fname =
"Database::set" )
00248 {
00249
$sql =
"UPDATE \"$table\" SET \"$var\" = '" .
00250
wfStrencode( $value ) .
"' WHERE ($cond)";
00251
return !!$this->query( $sql,
DB_WRITE, $fname );
00252 }
00253
00254
# Simple SELECT wrapper, returns a single field, input must be encoded
00255
# Usually aborts on failure
00256
# If errors are explicitly ignored, returns FALSE on failure
00257 function
get( $table, $var, $cond, $fname =
"Database::get" )
00258 {
00259 $from=$table?
" FROM \"$table\" ":
"";
00260 $where=$cond?
" WHERE ($cond)":
"";
00261
00262
$sql =
"SELECT $var $from $where";
00263
00264 $result = $this->query( $sql,
DB_READ, $fname );
00265
00266 $ret =
"";
00267
if ( pg_num_rows( $result ) > 0 ) {
00268
$s = pg_fetch_array( $result );
00269 $ret =
$s[0];
00270 pg_free_result( $result );
00271 }
00272
return $ret;
00273 }
00274
00275
# More complex SELECT wrapper, single row only
00276
# Aborts or returns FALSE on error
00277
# Takes an array of selected variables, and a condition map, which is ANDed
00278
# e.g. getArray( "cur", array( "cur_id" ), array( "cur_namespace" => 0, "cur_title" => "Astronomy" ) )
00279
# would return an object where $obj->cur_id is the ID of the Astronomy article
00280 function
getArray( $table, $vars, $conds, $fname =
"Database::getArray" )
00281 {
00282 $vars = implode(
",", $vars );
00283 $where =
Database::makeList( $conds,
LIST_AND );
00284
$sql =
"SELECT \"$vars\" FROM \"$table\" WHERE $where LIMIT 1";
00285
$res = $this->query( $sql, $fname );
00286
if (
$res ===
false || !$this->numRows( $res ) ) {
00287
return false;
00288 }
00289 $obj = $this->fetchObject( $res );
00290 $this->freeResult( $res );
00291
return $obj;
00292 }
00293
00294
# Removes most variables from an SQL query and replaces them with X or N for numbers.
00295
# It's only slightly flawed. Don't use for anything important.
00296 function
generalizeSQL( $sql )
00297 {
00298
# This does the same as the regexp below would do, but in such a way
00299
# as to avoid crashing php on some large strings.
00300
# $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
00301
00302
$sql = str_replace (
"\\\\",
"", $sql);
00303
$sql = str_replace (
"\\'",
"", $sql);
00304
$sql = str_replace (
"\\\"",
"", $sql);
00305
$sql = preg_replace (
"/'.*'/s",
"'X'", $sql);
00306
$sql = preg_replace ('/
".*"/s',
"'X'", $sql);
00307
00308
# All newlines, tabs, etc replaced by single space
00309
$sql = preg_replace (
"/\s+/",
" ", $sql);
00310
00311
# All numbers => N
00312
$sql = preg_replace ('/-?[0-9]+/s',
"N", $sql);
00313
00314
return $sql;
00315 }
00316
00317
# Determines whether a field exists in a table
00318
# Usually aborts on failure
00319
# If errors are explicitly ignored, returns NULL on failure
00320 function
fieldExists( $table, $field, $fname =
"Database::fieldExists" )
00321 {
00322
$res = $this->query(
"DESCRIBE '$table'",
DB_READ, $fname );
00323
if ( !
$res ) {
00324
return NULL;
00325 }
00326
00327 $found =
false;
00328
00329
while (
$row = $this->fetchObject( $res ) ) {
00330
if (
$row->Field == $field ) {
00331 $found =
true;
00332
break;
00333 }
00334 }
00335
return $found;
00336 }
00337
00338
# Determines whether an index exists
00339
# Usually aborts on failure
00340
# If errors are explicitly ignored, returns NULL on failure
00341 function
indexExists( $table, $index, $fname =
"Database::indexExists" )
00342 {
00343
$sql =
"SELECT indexname FROM pg_indexes WHERE tablename='$table'";
00344
$res = $this->query( $sql,
DB_READ, $fname );
00345
if ( !
$res ) {
00346
return NULL;
00347 }
00348
00349 $found =
false;
00350
00351
while (
$row = $this->fetchObject( $res ) ) {
00352
if (
$row->Key_name == $index ) {
00353 $found =
true;
00354
break;
00355 }
00356 }
00357
return $found;
00358 }
00359
00360 function
tableExists( $table )
00361 {
00362 $old = $this->
mIgnoreErrors;
00363 $this->
mIgnoreErrors =
true;
00364
$res = $this->query(
"SELECT 1 FROM '$table' LIMIT 1" );
00365 $this->
mIgnoreErrors = $old;
00366
if(
$res ) {
00367 $this->freeResult( $res );
00368
return true;
00369 }
else {
00370
return false;
00371 }
00372 }
00373
00374 function
fieldInfo( $table, $field )
00375 {
00376
$res = $this->query(
"SELECT * FROM '$table' LIMIT 1" );
00377
$n = pg_num_fields( $res );
00378
for(
$i = 0;
$i <
$n;
$i++ ) {
00379
00380
wfDebugDieBacktrace(
"Database::fieldInfo() error : mysql_fetch_field() not implemented for postgre" );
00381 $meta = mysql_fetch_field( $res, $i );
00382
if( $field == $meta->name ) {
00383
return $meta;
00384 }
00385 }
00386
return false;
00387 }
00388
00389
# INSERT wrapper, inserts an array into a table
00390
# Keys are field names, values are values
00391
# Usually aborts on failure
00392
# If errors are explicitly ignored, returns success
00393 function
insertArray( $table, $a, $fname =
"Database::insertArray" )
00394 {
00395 $sql1 =
"INSERT INTO \"$table\" (";
00396
$sql2 =
"VALUES (" .
Database::makeList( $a );
00397
$first =
true;
00398 foreach ( $a as $field => $value ) {
00399
if ( !
$first ) {
00400 $sql1 .=
",";
00401 }
00402
$first =
false;
00403 $sql1 .= $field;
00404 }
00405
$sql =
"$sql1) $sql2)";
00406
return !!$this->query( $sql, $fname );
00407 }
00408
00409
# A cross between insertArray and getArray, takes a condition array and a SET array
00410 function
updateArray( $table, $values, $conds, $fname =
"Database::updateArray" )
00411 {
00412
$sql =
"UPDATE '$table' SET " . $this->
makeList( $values,
LIST_SET );
00413
$sql .=
" WHERE " . $this->
makeList( $conds,
LIST_AND );
00414 $this->query( $sql, $fname );
00415 }
00416
00417
# Makes a wfStrencoded list from an array
00418
# $mode: LIST_COMMA - comma separated, no field names
00419
# LIST_AND - ANDed WHERE clause (without the WHERE)
00420
# LIST_SET - comma separated with field names, like a SET clause
00421 function
makeList( $a, $mode = LIST_COMMA )
00422 {
00423
$first =
true;
00424
$list =
"";
00425 foreach ( $a as $field => $value ) {
00426
if ( !
$first ) {
00427
if ( $mode ==
LIST_AND ) {
00428
$list .=
" AND ";
00429 }
else {
00430
$list .=
",";
00431 }
00432 }
else {
00433
$first =
false;
00434 }
00435
if ( $mode ==
LIST_AND || $mode ==
LIST_SET ) {
00436
$list .=
"$field=";
00437 }
00438
if ( !is_numeric( $value ) ) {
00439
$list .=
"'" .
wfStrencode( $value ) .
"'";
00440 }
else {
00441
$list .= $value;
00442 }
00443 }
00444
return $list;
00445 }
00446
00447 function
startTimer( $timeout )
00448 {
00449 global
$IP;
00450
wfDebugDieBacktrace(
"Database::startTimer() error : mysql_thread_id() not implemented for postgre" );
00451
$tid = mysql_thread_id( $this->mConn );
00452 exec(
"php $IP/killthread.php $timeout $tid &>/dev/null &" );
00453 }
00454
00455 function
stopTimer()
00456 {
00457 }
00458
00459 }
00460
00461
#------------------------------------------------------------------------------
00462
# Global functions
00463
#------------------------------------------------------------------------------
00464
00465
00466
00467 function
wfEmergencyAbort( &$conn ) {
00468 global
$wgTitle,
$wgUseFileCache,
$title,
$wgInputEncoding,
$wgSiteNotice,
$wgOutputEncoding;
00469
00470 header(
"Content-type: text/html; charset=$wgOutputEncoding" );
00471 $msg =
$wgSiteNotice;
00472
if($msg ==
"") $msg =
wfMsgNoDB(
"noconnect" );
00473 $text = $msg;
00474
00475
if(
$wgUseFileCache) {
00476
if(
$wgTitle) {
00477 $t =&
$wgTitle;
00478 }
else {
00479
if(
$title) {
00480 $t = Title::newFromURL( $
title );
00481 } elseif (@$_REQUEST['search']) {
00482 $search = $_REQUEST['search'];
00483 echo
wfMsgNoDB(
"searchdisabled" );
00484 echo
wfMsgNoDB(
"googlesearch", htmlspecialchars( $search ), $wgInputEncoding );
00485
wfAbruptExit();
00486 }
else {
00487 $t = Title::newFromText(
wfMsgNoDB(
"mainpage" ) );
00488 }
00489 }
00490
00491 $cache =
new CacheManager( $t );
00492
if( $cache->isFileCached() ) {
00493 $msg =
"<p style='color: red'><b>$msg<br />\n" .
00494
wfMsgNoDB(
"cachederror" ) .
"</b></p>\n";
00495
00496 $tag =
"<div id='article'>";
00497 $text = str_replace(
00498 $tag,
00499 $tag . $msg,
00500 $cache->fetchPageText() );
00501 }
00502 }
00503
00504
00505 header(
"Cache-control: none" );
00506 header(
"Pragma: nocache" );
00507 echo $text;
00508
wfAbruptExit();
00509 }
00510
00511 function
wfStrencode( $s )
00512 {
00513
return pg_escape_string( $s );
00514 }
00515
00516
# Use PostgreSQL timestamp without timezone data type
00517 function
wfTimestamp2Unix( $ts ) {
00518
return gmmktime( ( (
int)substr( $ts, 11, 2) ),
00519 (
int)substr( $ts, 14, 2 ), (
int)substr( $ts, 17, 2 ),
00520 (
int)substr( $ts, 5, 2 ), (
int)substr( $ts, 8, 2 ),
00521 (
int)substr( $ts, 0, 4 ) );
00522 }
00523
00524 function
wfUnix2Timestamp( $unixtime ) {
00525
return gmdate(
"Y-m-d H:i:s", $unixtime );
00526 }
00527
00528 function
wfTimestampNow() {
00529
# return NOW
00530
return gmdate(
"Y-m-d H:i:s" );
00531 }
00532
00533
# Sorting hack for MySQL 3, which doesn't use index sorts for DESC
00534 function
wfInvertTimestamp( $ts ) {
00535 $ts=preg_replace(
"/\D/",
"",$ts);
00536
return strtr(
00537 $ts,
00538
"0123456789",
00539
"9876543210"
00540 );
00541 }
00542
00543 function
wfLimitResult( $limit, $offset ) {
00544
return " LIMIT $limit ".(is_numeric($offset)?
" OFFSET {$offset} ":
"");
00545 }
00546
00547 ?>