From a4c652b0ff1c62434180c257fec8438903551115 Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Sat, 10 Jan 2004 16:44:31 +0000 Subject: [PATCH] object-oriented database connections --- includes/Database.php | 385 +++++++++++++++++++++++++++++++++ includes/DatabaseFunctions.php | 320 ++++++++------------------- includes/GlobalFunctions.php | 13 ++ 3 files changed, 492 insertions(+), 226 deletions(-) create mode 100644 includes/Database.php diff --git a/includes/Database.php b/includes/Database.php new file mode 100644 index 0000000000..c243b5dd17 --- /dev/null +++ b/includes/Database.php @@ -0,0 +1,385 @@ +mFailFunction, $function ); } + + # Output page, used for reporting errors + # FALSE means discard output + function &setOutputPage( &$out ) { return wfSetRef( $this->mOut, $out ); } + + # Boolean, controls output of large amounts of debug information + function setDebug( $debug ) { return wfSetVar( $this->mDebug, $debug ); } + + # Turns buffering of SQL result sets on (true) or off (false). Default is + # "on" and it should not be changed without good reasons. + function setBufferResults( $buffer ) { return wfSetVar( $this->mBufferResults, $buffer ); } + + # Turns on (false) or off (true) the automatic generation and sending + # of a "we're sorry, but there has been a database error" page on + # database errors. Default is on (false). When turned off, the + # code should use wfLastErrno() and wfLastError() to handle the + # situation as appropriate. + function setIgnoreErrors( $ignoreErrors ) { return wfSetVar( $this->mIgnoreErrors, $ignoreErrors ); } + + # Get functions + + function lastQuery() { return $this->mLastQuery; } + +#------------------------------------------------------------------------------ +# Other functions +#------------------------------------------------------------------------------ + + function Database() + { + global $wgOut; + $this->mOut = $wgOut; + + } + + /* static */ function newFromParams( $server, $user, $password, $dbName, + $failFunction = false, $debug = false, $bufferResults = true, $ignoreErrors = false ) + { + $db = new Database; + $db->mFailFunction = $failFunction; + $db->mIgnoreErrors = $ignoreErrors; + $db->mDebug = $debug; + $db->open( $server, $user, $password, $dbName ); + return $db; + } + + # Usually aborts on failure + # If the failFunction is set to a non-zero integer, returns success + function open( $server, $user, $password, $dbName ) + { + global $wgEmergencyContact; + + $this->close(); + $this->mServer = $server; + $this->mUser = $user; + $this->mPassword = $password; + $this->mDbName = $dbName; + + $success = false; + + @$this->mConn = mysql_connect( $server, $user, $password ); + if ( $this->mConn !== false ) { + $success = @mysql_select_db( $dbName, $this->mConn ); + if ( !$success ) { + wfDebug( "Error selecting database \"$dbName\": " . $this->lastError() . "\n" ); + } + } else { + wfDebug( "DB connect error: " . $this->lastError() . "\n" ); + wfDebug( "Server: $server, User: $user, Password: " . + substr( $password, 0, 3 ) . "...\n" ); + $success = false; + } + + if ( !$success ) { + $this->reportConnectionError(); + $this->close(); + } + return $success; + } + + # Closes a database connection, if it is open + # Returns success, true if already closed + function close() + { + if ( $this->mConn ) { + return mysql_close( $this->mConn ); + } else { + return true; + } + } + + /* private */ function reportConnectionError( $msg = "") + { + if ( $this->mFailFunction ) { + if ( !is_int( $this->mFailFunction ) ) { + $this->$mFailFunction( $this ); + } + } else { + wfEmergencyAbort( $this ); + } + } + + # Usually aborts on failure + # If errors are explicitly ignored, returns success + function query( $sql, $db, $fname = "" ) + { + 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" ); + } + if( $this->mBufferResults ) { + $ret = mysql_query( $sql, $this->mConn ); + } else { + $ret = mysql_unbuffered_query( $sql, $this->mConn ); + } + + if ( false === $ret ) { + if( $this->mIgnoreErrors ) { + wfDebug("SQL ERROR (ignored): " . mysql_error( $this->mConn ) . "\n"); + } else { + wfDebug("SQL ERROR: " . mysql_error( $this->mConn ) . "\n"); + if ( $this->mOut ) { + $this->mOut->databaseError( $fname ); // calls wfAbruptExit() + } + } + } + + if ( $wgProfiling ) { + wfProfileOut( $profName ); + } + return $ret; + } + + function freeResult( $res ) { mysql_free_result( $res ); } + function fetchObject( $res ) { return mysql_fetch_object( $res ); } + function numRows( $res ) { return mysql_num_rows( $res ); } + function numFields( $res ) { return mysql_num_fields( $res ); } + function fieldName( $res, $n ) { return mysql_field_name( $res, $n ); } + function insertId() { return mysql_insert_id( $this->mConn ); } + function dataSeek( $res, $row ) { return mysql_data_seek( $res, $row ); } + function lastErrno() { return mysql_errno( $this->mConn ); } + function lastError() { return mysql_error( $this->mConn ); } + function affectedRows() { return mysql_affected_rows( $this->mConn ); } + + # Simple UPDATE wrapper + # Usually aborts on failure + # If errors are explicitly ignored, returns success + function set( $table, $var, $value, $cond, $fname = "Database::set" ) + { + $sql = "UPDATE $table SET $var = '" . + wfStrencode( $value ) . "' WHERE ($cond)"; + return !!$this->query( $sql, DB_WRITE, $fname ); + } + + # Simple SELECT wrapper + # Usually aborts on failure + # If errors are explicitly ignored, returns FALSE on failure + function get( $table, $var, $cond, $fname = "Database::get" ) + { + $sql = "SELECT $var FROM $table WHERE ($cond)"; + $result = $this->query( $sql, DB_READ, $fname ); + + $ret = ""; + if ( mysql_num_rows( $result ) > 0 ) { + $s = mysql_fetch_object( $result ); + $ret = $s->$var; + mysql_free_result( $result ); + } + return $ret; + } + + # Removes most variables from an SQL query and replaces them with X or N for numbers. + # It's only slightly flawed. Don't use for anything important. + /* static */ function generalizeSQL( $sql ) + { + # This does the same as the regexp below would do, but in such a way + # as to avoid crashing php on some large strings. + # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); + + $sql = str_replace ( "\\\\", "", $sql); + $sql = str_replace ( "\\'", "", $sql); + $sql = str_replace ( "\\\"", "", $sql); + $sql = preg_replace ("/'.*'/s", "'X'", $sql); + $sql = preg_replace ('/".*"/s', "'X'", $sql); + + # All newlines, tabs, etc replaced by single space + $sql = preg_replace ( "/\s+/", " ", $sql); + + # All numbers => N + $sql = preg_replace ('/-?[0-9]+/s', "N", $sql); + + return $sql; + } + + # Determines whether a field exists in a table + # Usually aborts on failure + # If errors are explicitly ignored, returns NULL on failure + function fieldExists( $table, $field, $fname = "Database::fieldExists" ) + { + $res = $this->query( "DESCRIBE $table", DB_READ, $fname ); + if ( !$res ) { + return NULL; + } + + $found = false; + + while ( $row = $this->fetchObject( $res ) ) { + if ( $row->Field == $field ) { + $found = true; + break; + } + } + return $found; + } + + # Determines whether an index exists + # Usually aborts on failure + # If errors are explicitly ignored, returns NULL on failure + function indexExists( $table, $index, $fname = "Database::indexExists" ) + { + $sql = "SHOW INDEXES FROM $table"; + $res = $this->query( $sql, DB_READ, $fname ); + if ( !$res ) { + return NULL; + } + + $found = false; + + while ( $row = $this->fetchObject( $res ) ) { + if ( $row->Key_name == $index ) { + $found = true; + break; + } + } + return $found; + } + + # INSERT wrapper, inserts an array into a table + # Keys are field names, values are values + # Usually aborts on failure + # If errors are explicitly ignored, returns success + function insertArray( $table, $a, $fname = "Database::insertArray" ) + { + $sql1 = "INSERT INTO $table ("; + $sql2 = "VALUES ("; + $first = true; + foreach ( $a as $field => $value ) { + if ( $first ) { + $sql1 .= ","; + $sql2 .= ","; + $first = false; + } + $sql1 .= $field; + if ( is_string( $value ) ) { + $sql2 .= "'" . wfStrencode( $value ) . "'"; + } else { + $sql2 .= $value; + } + } + $sql = "$sql1) $sql2)"; + return !!$this->query( $sql, DB_WRITE, $fname ); + } +} + +#------------------------------------------------------------------------------ +# Global functions +#------------------------------------------------------------------------------ + +/* Standard fail function, called by default when a connection cannot be established + Displays the file cache if possible */ +function wfEmergencyAbort( &$conn ) { + global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; + + header( "Content-type: text/html; charset=$wgOutputEncoding" ); + if($msg == "") $msg = wfMsgNoDB( "noconnect" ); + $text = $msg; + + if($wgUseFileCache) { + if($wgTitle) { + $t =& $wgTitle; + } else { + if($title) { + $t = Title::newFromURL( $title ); + } elseif ($_REQUEST['search']) { + $search = $_REQUEST['search']; + echo wfMsgNoDB( "searchdisabled", htmlspecialchars( $search ), $wgInputEncoding ); + wfAbruptExit(); + } else { + $t = Title::newFromText( wfMsgNoDB( "mainpage" ) ); + } + } + + $cache = new CacheManager( $t ); + if( $cache->isFileCached() ) { + $msg = "

$msg
\n" . + wfMsgNoDB( "cachederror" ) . "

\n"; + + $tag = "
"; + $text = str_replace( + $tag, + $tag . $msg, + $cache->fetchPageText() ); + } + } + + /* Don't cache error pages! They cause no end of trouble... */ + header( "Cache-control: none" ); + header( "Pragma: nocache" ); + echo $text; + wfAbruptExit(); +} + +function wfStrencode( $s ) +{ + return addslashes( $s ); +} + +# Ideally we'd be using actual time fields in the db +function wfTimestamp2Unix( $ts ) { + return gmmktime( ( (int)substr( $ts, 8, 2) ), + (int)substr( $ts, 10, 2 ), (int)substr( $ts, 12, 2 ), + (int)substr( $ts, 4, 2 ), (int)substr( $ts, 6, 2 ), + (int)substr( $ts, 0, 4 ) ); +} + +function wfUnix2Timestamp( $unixtime ) { + return gmdate( "YmdHis", $unixtime ); +} + +function wfTimestampNow() { + # return NOW + return gmdate( "YmdHis" ); +} + +# Sorting hack for MySQL 3, which doesn't use index sorts for DESC +function wfInvertTimestamp( $ts ) { + return strtr( + $ts, + "0123456789", + "9876543210" + ); +} +?> diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php index a4cbe27d63..b3208e168b 100644 --- a/includes/DatabaseFunctions.php +++ b/includes/DatabaseFunctions.php @@ -1,152 +1,54 @@ If this error persists after reloading and clearing " . - "your browser cache, please notify the Wikipedia developers.

"; - - if ( $altuser != "" ) { - $serve = ($altserver ? $altserver : $wgDBserver ); - $db = ($altdb ? $altdb : $wgDBname ); - $wgDBconnection = mysql_connect( $serve, $altuser, $altpassword ) - or die( "bad sql user" ); - mysql_select_db( $db, $wgDBconnection ) or die( - htmlspecialchars(mysql_error()) ); - } - - if ( ! $wgDBconnection ) { - @$wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser, $wgDBpassword ) - or wfEmergencyAbort(); - - @mysql_select_db( $wgDBname, $wgDBconnection ) - or wfEmergencyAbort(); - } - # mysql_ping( $wgDBconnection ); - return $wgDBconnection; -} - -/* Call this function if we couldn't contact the database... - We'll try to use the cache to display something in the meantime */ -function wfEmergencyAbort( $msg = "" ) { - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; - - header( "Content-type: text/html; charset=$wgOutputEncoding" ); - if($msg == "") $msg = wfMsgNoDB( "noconnect" ); - $text = $msg; - - if($wgUseFileCache) { - if($wgTitle) { - $t =& $wgTitle; - } else { - if($title) { - $t = Title::newFromURL( $title ); - } elseif ($_REQUEST['search']) { - $search = $_REQUEST['search']; - echo wfMsgNoDB( "searchdisabled", htmlspecialchars( $search ), $wgInputEncoding ); - wfAbruptExit(); - } else { - $t = Title::newFromText( wfMsgNoDB( "mainpage" ) ); - } - } - - $cache = new CacheManager( $t ); - if( $cache->isFileCached() ) { - $msg = "

$msg
\n" . - wfMsgNoDB( "cachederror" ) . "

\n"; - - $tag = "
"; - $text = str_replace( - $tag, - $tag . $msg, - $cache->fetchPageText() ); - } - } - - /* Don't cache error pages! They cause no end of trouble... */ - header( "Cache-control: none" ); - header( "Pragma: nocache" ); - echo $text; - wfAbruptExit(); -} +# NB: This file follows a connect on demand scheme. Do +# not access the $wgDatabase variable directly, use wfGetDB() +# Query the database # $db: DB_READ = -1 read from slave (or only server) # DB_WRITE = -2 write to master (or only server) # 0,1,2,... query a database with a specific index # Replication is not actually implemented just yet -function wfQuery( $sql, $db, $fname = "" ) -{ - global $wgLastDatabaseQuery, $wgOut, $wgDebugDumpSql, $wgBufferSQLResults, - $wgIgnoreSQLErrors, $wgProfiling; +# Usually aborts on failure +# If errors are explicitly ignored, returns success - if ( $wgProfiling ) { - # wfGeneralizeSQL will probably cut down the query to reasonable - # logging size most of the time. The substr is really just a sanity check. - $profName = "wfQuery: " . substr( wfGeneralizeSQL( $sql ), 0, 255 ); - wfProfileIn( $profName ); - } +include_once( "Database.php" ); +function wfQuery( $sql, $db, $fname = "" ) +{ + global $wgDatabase, $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, + $wgDebugDumpSql, $wgBufferSQLResults, $wgIgnoreSQLErrors; + if ( !is_numeric( $db ) ) { # Someone has tried to call this the old way $wgOut->fatalError( wfMsgNoDB( "wrong_wfQuery_params", $db, $sql ) ); } - - $wgLastDatabaseQuery = $sql; - - if( $wgDebugDumpSql ) { - $sqlx = substr( $sql, 0, 500 ); - $sqlx = wordwrap(strtr($sqlx,"\t\n"," ")); - wfDebug( "SQL: $sqlx\n" ); - } - $conn = wfGetDB(); - if( $wgBufferSQLResults ) { - $ret = mysql_query( $sql, $conn ); - } else { - $ret = mysql_unbuffered_query( $sql, $conn ); - } + $db = wfGetDB(); + return $db->query( $sql, $db, $fname ); +} - if ( false === $ret ) { - if( $wgIgnoreSQLErrors ) { - wfDebug("SQL ERROR (ignored): " . mysql_error( $conn ) . "\n"); - } else { - wfDebug("SQL ERROR: " . mysql_error( $conn ) . "\n"); - $wgOut->databaseError( $fname ); // calls wfAbruptExit() - } - } - - if ( $wgProfiling ) { - wfProfileOut( $profName ); +# Connect on demand +function wfGetDB() +{ + global $wgDatabase, $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, + $wgDebugDumpSql, $wgBufferSQLResults, $wgIgnoreSQLErrors; + if ( !$wgDatabase ) { + $wgDatabase = Database::newFromParams( $wgDBserver, $wgDBuser, $wgDBpassword, + $wgDBname, false, $wgDebugDumpSql, $wgBufferSQLResults, $wgIgnoreSQLErrors ); } - return $ret; + return $wgDatabase; } - + # Turns buffering of SQL result sets on (true) or off (false). Default is # "on" and it should not be changed without good reasons. # Returns the previous state. -function wfBufferSQLResults( $newstate ){ - global $wgBufferSQLResults; - $oldstate = $wgBufferSQLResults; - $wgBufferSQLResults = $newstate; - return $oldstate; +function wfBufferSQLResults( $newstate ) +{ + $db = wfGetDB(); + return $db->setBufferResults( $newstate ); } # Turns on (false) or off (true) the automatic generation and sending @@ -156,133 +58,99 @@ function wfBufferSQLResults( $newstate ){ # situation as appropriate. # Returns the previous state. -function wfIgnoreSQLErrors( $newstate ){ - global $wgIgnoreSQLErrors; - $oldstate = $wgIgnoreSQLErrors; - $wgIgnoreSQLErrors = $newstate; - return $oldstate; -} - -function wfFreeResult( $res ) { mysql_free_result( $res ); } -function wfFetchObject( $res ) { return mysql_fetch_object( $res ); } -function wfNumRows( $res ) { return mysql_num_rows( $res ); } -function wfNumFields( $res ) { return mysql_num_fields( $res ); } -function wfFieldName( $res, $n ) { return mysql_field_name( $res, $n ); } -function wfInsertId() { return mysql_insert_id( wfGetDB() ); } -function wfDataSeek( $res, $row ) { return mysql_data_seek( $res, $row ); } -function wfLastErrno() { return mysql_errno(); } -function wfLastError() { return mysql_error(); } -function wfAffectedRows() { return mysql_affected_rows( wfGetDB() ); } - -function wfLastDBquery() +function wfIgnoreSQLErrors( $newstate ) { - global $wgLastDatabaseQuery; - return $wgLastDatabaseQuery; + $db = wfGetDB(); + return $db->setIgnoreErrors( $newstate ); } -function wfSetSQL( $table, $var, $value, $cond ) -{ - $sql = "UPDATE $table SET $var = '" . - wfStrencode( $value ) . "' WHERE ($cond)"; - wfQuery( $sql, DB_WRITE, "wfSetSQL" ); +function wfFreeResult( $res ) +{ + $db = wfGetDB(); + $db->freeResult( $res ); } -function wfGetSQL( $table, $var, $cond ) -{ - $sql = "SELECT $var FROM $table WHERE ($cond)"; - $result = wfQuery( $sql, DB_READ, "wfGetSQL" ); - - $ret = ""; - if ( mysql_num_rows( $result ) > 0 ) { - $s = mysql_fetch_object( $result ); - $ret = $s->$var; - mysql_free_result( $result ); - } - return $ret; +function wfFetchObject( $res ) +{ + $db = wfGetDB(); + return $db->fetchObject( $res ); } -function wfStrencode( $s ) -{ - return addslashes( $s ); +function wfNumRows( $res ) +{ + $db = wfGetDB(); + return $db->numRows( $res ); } -# Ideally we'd be using actual time fields in the db -function wfTimestamp2Unix( $ts ) { - return gmmktime( ( (int)substr( $ts, 8, 2) ), - (int)substr( $ts, 10, 2 ), (int)substr( $ts, 12, 2 ), - (int)substr( $ts, 4, 2 ), (int)substr( $ts, 6, 2 ), - (int)substr( $ts, 0, 4 ) ); +function wfNumFields( $res ) +{ + $db = wfGetDB(); + return $db->numFields( $res ); } -function wfUnix2Timestamp( $unixtime ) { - return gmdate( "YmdHis", $unixtime ); +function wfFieldName( $res, $n ) +{ + $db = wfGetDB(); + return $db->fieldName( $res, $n ); } -function wfTimestampNow() { - # return NOW - return gmdate( "YmdHis" ); +function wfInsertId() +{ + $db = wfGetDB(); + return $db->insertId(); +} +function wfDataSeek( $res, $row ) +{ + $db = wfGetDB(); + return $db->dataSeek( $res, $row ); } -# Sorting hack for MySQL 3, which doesn't use index sorts for DESC -function wfInvertTimestamp( $ts ) { - return strtr( - $ts, - "0123456789", - "9876543210" - ); +function wfLastErrno() +{ + $db = wfGetDB(); + return $db->lastErrno(); } -# Removes most variables from an SQL query and replaces them with X or N for numbers. -# It's only slightly flawed. Don't use for anything important. -function wfGeneralizeSQL( $sql ) -{ - # This does the same as the regexp below would do, but in such a way - # as to avoid crashing php on some large strings. - # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); +function wfLastError() +{ + $db = wfGetDB(); + return $db->lastError(); +} - $sql = str_replace ( "\\\\", "", $sql); - $sql = str_replace ( "\\'", "", $sql); - $sql = str_replace ( "\\\"", "", $sql); - $sql = preg_replace ("/'.*'/s", "'X'", $sql); - $sql = preg_replace ('/".*"/s', "'X'", $sql); +function wfAffectedRows() +{ + $db = wfGetDB(); + return $db->affectedRows(); +} - # All newlines, tabs, etc replaced by single space - $sql = preg_replace ( "/\s+/", " ", $sql); +function wfLastDBquery() +{ + $db = wfGetDB(); + return $db->lastQuery(); +} - # All numbers => N - $sql = preg_replace ('/-?[0-9]+/s', "N", $sql); +function wfSetSQL( $table, $var, $value, $cond ) +{ + $db = wfGetDB(); + return $db->set( $table, $var, $value, $cond ); +} - return $sql; +function wfGetSQL( $table, $var, $cond ) +{ + $db = wfGetDB(); + return $db->get( $table, $var, $cond ); } function wfFieldExists( $table, $field ) { - $fname = "wfFieldExists"; - $res = wfQuery( "DESCRIBE $table", DB_READ, $fname ); - $found = false; - - while ( $row = wfFetchObject( $res ) ) { - if ( $row->Field == $field ) { - $found = true; - break; - } - } - return $found; + $db = wfGetDB(); + return $db->fieldExists( $table, $field ); } function wfIndexExists( $table, $index ) { - global $wgDBname; - $fname = "wfIndexExists"; - $sql = "SHOW INDEXES FROM $table"; - $res = wfQuery( $sql, DB_READ, $fname ); - $found = false; - while ( $row = wfFetchObject( $res ) ) { - if ( $row->Key_name == $index ) { - $found = true; - break; - } - } - return $found; + $db = wfGetDB(); + return $wgDatabase->indexExists( $table, $index ); } + ?> diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 917735d55f..e6e2b97922 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -675,5 +675,18 @@ function wfHtmlEscapeFirst( $text ) { return "&#$ord;$newText"; } +function wfSetVar( &$dest, $source ) +{ + $temp = $dest; + $dest = $source; + return $temp; +} + +function &wfSetRef( &$dest, $source ) +{ + $temp =& $dest; + $dest =& $source; + return $temp; +} ?> -- 2.20.1