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