<?php
//
// +---------------------------------------------------------------------------+
// | Nitro :: NitroDB |
// +---------------------------------------------------------------------------+
// | Copyright (c) 2003 June Systems BV |
// +---------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify it |
// | under the terms of the GNU Lesser General Public License as published by |
// | the Free Software Foundation; either version 2.1 of the License, or (at |
// | your option) any later version. |
// | |
// | This library is distributed in the hope that it will be useful, but |
// | WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser |
// | General Public License for more details. |
// | |
// | You should have received a copy of the GNU Lesser General Public License |
// | along with this library; if not, write to the Free Software Foundation, |
// | Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
// +---------------------------------------------------------------------------+
// | Authors: Siggi Oskarsson <hide@address.com> |
// +---------------------------------------------------------------------------+
//
// $Id: NitroDB.inc.php 229 2008-04-17 09:20:31Z oli $
//
// Nitro database base class
//
/**
* This file contains the main DB class of Nitro
*
* @package Nitro
* @subpackage DB
* @author Siggi Oskarsson
* @version $Revision: 1.19 $
* @copyright 2004 June Systems BV
*/
/**
* Define fetchmodes used in the DB connection
*/
define("NITRODB_NUM", 1);
define("NITRODB_ASSOC", 2);
define("NITRODB_BOTH", 3);
define("DB_FETCHMODE_NUM", NITRODB_NUM);
define("DB_FETCHMODE_ASSOC", NITRODB_ASSOC);
define("DB_FETCHMODE_BOTH", NITRODB_BOTH);
include_once "Nitro/NitroDB/Nitro.inc.php";
include_once "Nitro/Transaction.inc.php";
/**
* Main DB class definition
*
* This is the prototype DB class definition that is actually called when
* the DB layer is used. This class initializes and uses sub classes (like MySQL) for
* the actual calls to the database.
*
* <CODE>
* $DBConnection = new DB('mysql://root:@localhost/Nitro');
* if ($DBConnection->Error) {
* echo "ERROR: Failed connecting to database";
* } else {
* $Result = $DBConnection->query('SELECT * FROM User');
* while($Row = $Result->FetchArray()) {
* echo $Row['Name'].'<BR>';
* }
* $Result->free();
* }
*
* $UserName = $DBConnection->getOne('SELECT Name FROM User WHERE UserID = 1');
* $AllUserNames = $DBConnection->getCol('SELECT Name FROM User');
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1');
* </CODE>
*
* @package Nitro
* @subpackage DB
*/
class DB {
/**
* @var array Array of DB DSN (i.e. database_type://username:[password]@host/database_name)
*/
var $DSN = Array();
/**
* @var string Error string
*/
var $Error;
/**
* @var array Database type (i.e. MySQL)
*/
var $DBType = Array();
/**
* @var array Username for connection
*/
var $Username = Array();
/**
* @var array Password for connection
*/
var $Password = Array();
/**
* @var array Host for connection
*/
var $Host = Array();
/**
* @var array Port on Host for connection
*/
var $Port = Array();
/**
* @var array Database for connection
*/
var $Database = Array();
/**
* @var array Database connection options
*/
var $Options = Array();
/**
* @var array Force the creation of a new link when connectiong
*/
var $NewLink = Array();
/**
* @var array Array of Database handler objects
*/
var $DBHandler = Array();
/**
* @var array Database connection
*/
var $ConnectionID = Array();
/**
* @var int Index of handler used for last query
*/
var $LastIndex;
/**
* @var string Load Balance Method to use in determining server to use (Random, RandomFixed (default), RoundRobin, Load, Weighted)
*/
var $BalanceMethod = 'RandomFixed';
/**
* @var array Cache array of list of tables in database
*/
var $TableList;
/**
* Class constructor function
*
* @param string $DSN DSN to use for connection (database_type://username:[password]@host/database_name)
* @param boolean $NewLink Force new link or allow to reuse link if host and login the same
* @param boolean $AutoConnect Automatically connect to the database
* @access public
*/
function DB($DSN, $NewLink = FALSE, $AutoConnect = FALSE)
{
DebugGroup("DB", "DB", "Initializing DB connection from DSN", __FILE__, __LINE__, DEBUG_SQL_OK);
$this->AddHandler($DSN, $NewLink, $AutoConnect);
if ($this->Options[0]['BalanceMethod']) $this->BalanceMethod = $this->Options[0]['BalanceMethod'];
if (!$this->Options[0]['Mode']) $this->Options[0]['Mode'] = 'RW'; // first server is Master/RW unless otherwise specified
DebugCloseGroup(DEBUG_SQL_OK);
return $RV;
}
/**
* Add a database Handler to database object
*
* The database handler added can be type of RO (read-only) or RW (read-write).
*
* @param string $DSN DSN to use for connection (database_type://username:[password]@host/database_name)
* @param boolean $NewLink Force new link or allow to reuse link if host and login the same
* @param boolean $AutoConnect Automatically connect to the database
* @access public
*/
function AddHandler($DSN, $NewLink = FALSE, $AutoConnect = FALSE)
{
$Index = (int)count($this->DSN);
$this->DSN[$Index] = $DSN;
$this->NewLink[$Index] = $NewLink;
if ($this->SetDSN($DSN, $Index)) {
if ($AutoConnect) {
$this->Connect($Index);
} else {
$this->ConnectionID[$Index] = -1;
}
} else {
Debug("DB", "DB", "DSN is not valid $DSN ($this->Error)", __FILE__, __LINE__, DEBUG_SQL_ERR|DEBUG_APPL_ERR);
$this->Error = "DSN is not valid!";
$RV = FALSE;
}
$this->AllIndexes = array_keys($this->DSN);
return $RV;
}
/**
* Remove handler from AllIndex list which is used for balancing
*
* @param int $Index Index of server to remove
* @access public
*/
function RemoveHandler($Index)
{
$newAllIndexes = Array();
foreach($this->AllIndexes AS $id) {
if ($id != $Index) {
$newAllIndexes[] = $id;
}
}
$this->AllIndexes = $newAllIndexes;
}
/**
* Database connection function
*
* @param boolean $NewLink Force new link or allow to reuse link if host and login the same
* @access public
*/
function Connect($Index = 0)
{
DebugGroup("DB", "DB", "Connection to database ".$this->Host[$Index], __FILE__, __LINE__, DEBUG_SQL_OK);
include_once "Nitro/NitroDB/".$this->DBType[$Index].".inc.php";
eval('$this->DBHandler[$Index] = new DB_'.$this->DBType[$Index].'($this->Host[$Index], $this->Username[$Index], $this->Password[$Index], $this->Database[$Index], $this->Port[$Index]);');
if(!$this->ConnectionID[$Index] = $this->DBHandler[$Index]->Connect($this->NewLink[$Index])) {
Debug("DB", "DB", "Failed to create database connection to ".$this->Database[$Index]." on ".$this->Host[$Index], __FILE__, __LINE__, DEBUG_SQL_ERR);
$this->Error = "Failed to create database connection";
$RV = FALSE;
} else {
Debug("DB", "DB", "Connected to database ".$this->Database[$Index]." on ".$this->Host[$Index], __FILE__, __LINE__, DEBUG_SQL_OK);
if (array_key_exists('CheckSlaveStatus', $this->Options[$Index]) && $this->Options[$Index]['CheckSlaveStatus']) {
// TODO: Check whether slave is up2date and running
if (method_exists($this->DBHandler[$Index], 'CheckSlave') && $this->DBHandler[$Index]->CheckSlave()) {
$RV = TRUE;
} else {
$RV = FALSE;
}
} else {
$RV = TRUE;
}
}
DebugCloseGroup(DEBUG_SQL_OK);
return $RV;
}
/**
* Set DSN
*
* This function sets the class variables to the values matched by ParsedDSN().
* If the parsing failes this function will return FALSE and not set any class
* variables.
*
* @see ParseDSN()
* @param string $ParsedDSN Parsed DSN array to use for connection
* @return boolean Valid DSN string or not
* @access private
*/
function SetDSN($DSN, $Index = 0)
{
if ($ParsedDSN = $this->ParseDSN($DSN)) {
if (include_once("Nitro/NitroDB/".strtolower($ParsedDSN['DBType']).".inc.php")) {
$this->DBType[$Index] = strtolower($ParsedDSN['DBType']);
$this->Username[$Index] = $ParsedDSN['Username'];
$this->Password[$Index] = $ParsedDSN['Password'];
$this->Host[$Index] = $ParsedDSN['Host'];
$this->Port[$Index] = $ParsedDSN['Port'];
$this->Database[$Index] = $ParsedDSN['Database'];
$this->Options[$Index] = $ParsedDSN['Options'];
return TRUE;
} else {
$this->Error = "Database type not supported";
return FALSE;
}
} else {
$this->Error = "DSN not valid";
return FALSE;
}
}
/**
* Parse DSN
*
* This function parses a valid DSN string and returns it in an associative array.
*
* @param string $DSN DSN to use for connection (database_type://username:[password]@host/database_name)
* @return mixed Parsed DSN string in array or FALSE
* @access public
*/
function ParseDSN($DSN)
{
$RV = Array();
//TODO: better DSN parser with support for all options (i.e. ports etc.)
if (preg_match("'([^:]+)://(([^:^@]+)?(:([^@]*))?@)?([^/^:]+)(:([0-9]+))?/([^\?]*)\??(.*)?'", $DSN, $match)) {
$RV['DBType'] = strtolower($match[1]);
$RV['Username'] = $match[3];
$RV['Password'] = $match[5];
$RV['Host'] = $match[6];
$RV['Port'] = $match[8];
$RV['Database'] = $match[9];
$RV['Options'] = Array();
if ($match[10]) {
$tmp = explode('&', $match[10]);
foreach($tmp AS $t) {
$tmp2 = explode('=', $t);
$RV['Options'][$tmp2[0]] = $tmp2[1];
}
}
return $RV;
} else {
return FALSE;
}
}
/**
* Query database
*
* This function calls the query function of the database handle (DBHandler)
* and returns a Result object.
*
* <CODE>
* $Result = $DBConnection->query('SELECT * FROM User WHERE UserID = 1');
* </CODE>
*
* @param string $Query Query to run on database
* @param int $Limit Limit number of rows returned
* @param int $Offset Offset of rows limited by $Limit
* @param string $Mode Mode of query (RO or RW)
* @param int $Index Index of handler to use
* @return object Result object
*/
function query($Query, $Limit = 0, $Offset = 0, $Mode = FALSE, $Index = FALSE)
{
if ($Index === FALSE) {
if ($Mode === FALSE) $Mode = (ereg('^SELECT', $Query) ? 'RO' : 'RW'); // Try to guess mode based on query
$Indexes2Use = $this->GetIndexes($Mode);
$Index = $this->GetIndex2Use($Indexes2Use, $this->BalanceMethod);
}
// TODO: build in failover on error on server
if (DB::isDBHandler($this->DBHandler[$Index])) {
$Result = $this->DBHandler[$Index]->query($Query, $Limit, $Offset);
if ($Result === FALSE OR DB::isError($Result)) {
Debug(__CLASS__, __FUNCTION__, "Query failed: ".mysql_error(), __FILE__, __LINE__, DEBUG_SQL_ERR);
Debug(__CLASS__, __FUNCTION__, ereg_replace("[\r\n]+", ' ', $Query), __FILE__, __LINE__, DEBUG_SQL_ERR);
//$this->Error();
}
Debug(__CLASS__, __FUNCTION__, "Query Done on ".$this->DBHandler[$Index]->Host, __FILE__, __LINE__, DEBUG_SQL_OK);
return $Result;
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
/**
* Retreive insert id of last insert
*
* This function returns the id of the row added by the last insert
* query done through the current DB handler. This must be called after the
* insert query itself.
*
* <CODE>
* $DBConnection->query("INSERT INTO User SET Name = 'FirstName LastName'");
* $UserID = $DBConnection->InsertID();
* </CODE>
*
* @return int Insert id
*/
function InsertID($Index = 0)
{
if (DB::isDBHandler($this->DBHandler[$Index])) {
return $this->DBHandler[$Index]->InsertID();
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
/**
* Prepare Query for usage in db handler
*
* This function will prepare the query given to comply with the
* SQL syntax of the DB handler compared to MySQL. All MySQL specifics
* will be changed to comply.
*
* @param string $Query Query to prepare
* @param int $Index Index of handler to use (default = 0)
* @return string $Query Prepared query
*/
function PrepareQuery($Query, $Index = 0)
{
if (DB::isDBHandler($this->DBHandler[$Index])) {
return $this->DBHandler[$Index]->PrepareQuery($Query);
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
/**
* Get exactly one value from the database
*
* This function queries the database and returns a single value. This value is by
* default the value in the first column of the first row. It is also possible to
* retreive a value from a different row or column by using the arguments.
*
* <CODE>
* $UserName = $DBConnection->getOne('SELECT Name FROM User WHERE UserID = 1');
* // Get name of 5th user found
* $UserName = $DBConnection->getOne('SELECT Name FROM User', 5);
* // Get value of 3rd column of 5th user found
* $UserName = $DBConnection->getOne('SELECT * FROM User', 5, 3);
* </CODE>
*
* @param string $Query Query to run on database
* @param int $row Row of result set to return (default 1)
* @param int $col Column of result set to return (default 1)
* @return mixed Value found in database or (boolean) FALSE on failure
*/
function getOne($Query, $row = 1, $col = 1)
{
DebugGroup("DB", "getOne", $Query, __FILE__, __LINE__, DEBUG_SQL_OK);
$Result = $this->query($Query);
if ($Result === FALSE OR DB::isError($Result)) {
$RV = FALSE;
} else {
if ($Result->NumRows()) {
$RV = (string)$Result->fetchResult($row - 1,$col - 1);
Debug(__CLASS__, "getOne", "Result fetched for row $row and column $col", __FILE__, __LINE__, DEBUG_SQL_OK);
} else {
$RV = FALSE;
Debug(__CLASS__, "getOne", "No result", __FILE__, __LINE__, DEBUG_SQL_NOROWS);
}
}
if ($Result) $Result->free();
DebugCloseGroup(DEBUG_SQL_OK);
return $RV;
}
/**
* Get exactly one row from the database
*
* This function queries the database and returns a single row. This row is by
* default the first in the result. It is also possible to retreive a different row
* by using the row argument. It is also possible to control the type of array returned
* by using the FetchMode parameter.
*
* <CODE>
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1');
* // Get 5th row
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1', 5);
* // Get 5th row in an associative array (using column names)
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1', 5, NITRODB_ASSOC);
* // Get 5th row in an indexed array
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1', 5, NITRODB_NUM);
* // Get 5th row in an indexed and associative array (returns twice the amount of columns found!)
* $UserArray = $DBConnection->getRow('SELECT * FROM User WHERE UserID = 1', 5, NITRODB_BOTH);
* </CODE>
*
* @param string $Query Query to run on database
* @param int $row Row of result set to return (default 1)
* @param int $FetchMode Fetchmode to use in returning the row (default NITRODB_ASSOC)
* @return mixed Array of columns in row found in database or (boolean) FALSE on failure
*/
function getRow($Query, $row = 1, $FetchMode = NITRODB_ASSOC)
{
DebugGroup("DB", "getRow", $Query, __FILE__, __LINE__, DEBUG_SQL_OK);
$Result = $this->query($Query);
if ($Result === FALSE OR DB::isError($Result)) {
$RV = FALSE;
} else {
Debug(__CLASS__, "getRow", "Query Done", __FILE__, __LINE__, DEBUG_SQL_OK);
$n = 1;
$RV = FALSE;
while($Row = $Result->fetchRow($FetchMode)) {
if ($n == $row) {
Debug(__CLASS__, "getRow", "Row $row fetched", __FILE__, __LINE__, DEBUG_SQL_OK);
$RV = $Row;
break;
}
$n++;
}
}
if ($Result) $Result->free();
DebugCloseGroup(DEBUG_SQL_OK);
return $RV;
}
/**
* Get exactly one column from the database
*
* This function queries the database and returns a single column from multiple rows.
* This column is by default the first in the result. It is also possible to retreive
* a different column by using the column argument.
*
* <CODE>
* $AllUserNames = $DBConnection->getCol('SELECT Name FROM User');
* // Get the 3rd column
* $AllUserNames = $DBConnection->getCol('SELECT * FROM User', 3);
* </CODE>
*
* @param string $Query Query to run on database
* @param int $col Column of result set to return (default 1)
* @return mixed Array of column for rows found in database or (boolean) FALSE on failure
*/
function getCol($Query, $col = 1)
{
DebugGroup("DB", "getCol", $Query, __FILE__, __LINE__, DEBUG_SQL_OK);
$Result = $this->query($Query);
if ($Result === FALSE OR DB::isError($Result)) {
$RV = FALSE;
} else {
$RV = Array();
if (is_int($col)) {
$FetchMode = NITRODB_NUM;
$col = ($col > 0 ? $col - 1 : 0);
} else {
$FetchMode = NITRODB_ASSOC;
}
while($Row = $Result->fetchRow($FetchMode)) {
$RV[] = $Row[$col];
}
Debug(__CLASS__, "getCol", "Column $col fetched", __FILE__, __LINE__, DEBUG_SQL_OK);
}
if ($Result) $Result->free();
DebugCloseGroup(DEBUG_SQL_OK);
return $RV;
}
/**
* Get function to use in SQL for inserting date/time automatically
*
* @return string Function for use in SQL
*/
function getDateTimeFunction()
{
if (DB::isDBHandler($this->DBHandler[0])) {
return $this->DBHandler[0]->getDateTimeFunction();
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
/**
* Coming soon
*/
function getFunction($Function, $Parameters = FALSE)
{
if ($Parameters !== FALSE && is_array($Parameters)) {
$RV = $Function . "('" . implode("', '", array_values($Parameters)) . "')";
} elseif($Parameters !== FALSE && strlen($Parameters)) {
$RV = $Function . "('" . $this->escapeString($Parameters) . "')";
} else {
$RV = $Function . "()";
}
Debug(__CLASS__, __FUNCTION__, "DB::getFunction(" . $Function . ") with parameters: '" . (is_array($Parameters) ? implode("':'", array_values($Parameters)) : $Parameters) . "', result: " . $RV, __FILE__, __LINE__, DEBUG_SQL_OK);
return $RV;
}
/**
* Start a new transaction
*
* This function will use the database specific transaction commands to start
* a new transaction.
*
* @return boolean Transaction successfully started
*/
function TransactionStart()
{
return false; // no transaction support yet
}
/**
* Commit transaction
*
* This function will use the database specific transaction commands to commit
* the transaction started.
*
* @return boolean Transaction successfully commited
*/
function TransactionCommit()
{
return false; // no transaction support yet
}
/**
* Rollback transaction
*
* This function will use the database specific transaction commands to rollback
* the transaction started.
*
* @return boolean Transaction successfully rolled back
*/
function TransactionRollback()
{
return false; // no transaction support yet
}
/**
* List all tables in the database
*
* This function will return an array of the names of all the tables in the database
* connected to.
*
* <CODE>
* $Tables = $DBConnection->listTables();
* </CODE>
*
* Notice that this function caches the list of tables in the database for fast retrieval
* later. If you create a new table or delete a table and want to check the list again, set
* the $useCache variable to FALSE when calling the function.
*
* @param boolean $useCache Use cache list if available
* @param int $Index Index of handler to use
* @return array Array of table names in database
*/
function listTables($useCache = TRUE, $Index = FALSE)
{
if (isset($this->TableList) && $useCache === TRUE) {
return $this->TableList;
} else {
if ($Index === FALSE) {
$Mode = 'RO';
$Indexes2Use = $this->GetIndexes($Mode);
$Index = $this->GetIndex2Use($Indexes2Use, $this->BalanceMethod);
}
if (DB::isDBHandler($this->DBHandler[$Index])) {
$tables = $this->DBHandler[$Index]->listTables();
$this->TableList = $tables;
return $tables;
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
}
/**
* List all fields of the given table in the database
*
* This function will return an array of all the fields in the given table in the
* database connected to. The returned array is an associative array with the name of
* the field as a key and the value is another associative array with the properties
* of the table.
*
* <CODE>
* $Fields = $DBConnection->listFields($Table);
* </CODE>
*
* Notice that this function caches the list of fields in the table for fast retrieval
* later. If you create a new field or delete a field and want to check the list again, set
* the $useCache variable to FALSE when calling the function.
*
* @param string $Table Table name
* @param boolean $useCache Use cache list if available
* @param int $Index Index of handler to use
* @return array Array of fields in table
*/
function listFields($Table, $useCache = FALSE, $Index = FALSE)
{
if (isset($this->FieldList[$Table]) && $useCache === TRUE) {
return $this->FieldList[$Table];
} else {
if ($Index === FALSE) {
$Mode = 'RO';
$Indexes2Use = $this->GetIndexes($Mode);
$Index = $this->GetIndex2Use($Indexes2Use, $this->BalanceMethod);
}
if (DB::isDBHandler($this->DBHandler[$Index])) {
$fields = $this->DBHandler[$Index]->listFields($Table);
$this->FieldList[$Table] = $fields;
return $fields;
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
}
/**
* Escapes special characters in a string for use in a SQL statement
*
* This function will use the specific database escape funtion to escape the
* string before use in a query.
*
* <CODE>
* $Query = "INSERT INTO Table SET Field = '".$DBConnection->escapeString("test string")."'";
* $DBConnection->query($Query);
* </CODE>
*
* @param string $String String to escape
*/
function escapeString($String)
{
if (DB::isDBHandler($this->DBHandler[0])) {
return $this->DBHandler[0]->escapeString($String);
} else {
$this->Error = "Not a valid DB Handler initalized";
return FALSE;
}
}
/**
* Check whether object is a database error
*
* This function can be used to check whether the result returned by
* the query function is an error (an erro occurred during query).
*
* <CODE>
* $Result = $DBConnection->query('SELECT * FROM User WHERE UserID = 1');
* if ($Result === FALSE OR DB::isError($Result)) {
* echo 'ERROR: query failed.';
* exit;
* }
* </CODE>
*
* @param object $value Object to test for error
* @return boolean (TRUE|FALSE) for error
*/
function isError($value)
{
return (is_object($value) && $value->Error);
}
/**
* Check whether variable is a database connection
*
* This function can be used to check whether the given variable
* is a database connection.
*
* <CODE>
* if (!DB::isDBCon($DBConnection)) {
* echo 'ERROR: Not a database connection.';
* exit;
* }
* </CODE>
*
* @param object $value Variable to test for connection
* @return boolean (TRUE|FALSE) for database connection
*/
function isDBCon($value)
{
return (is_object($value) && $value->ConnectionID && $value->DSN);
}
/**
* Check whether variable is a database handler
*
* This function can be used to check whether the given variable
* is a valid database handler
*
* <CODE>
* if (!DB::isDBHandler($DBHandler)) {
* echo 'ERROR: Not a database handler.';
* exit;
* }
* </CODE>
*
* @param object $value Variable to test for hander
* @return boolean (TRUE|FALSE) for database handler
* @access private
*/
function isDBHandler($value)
{
return (is_object($value) && $value->ConnectionID && $value->DBHandlerID);
}
/**
* Get all indexes in this object with specified MOde
*
* @param string $Mode Mode of handler indexes to return (RO or RW)
* @return array Indexes with mode
* @access public
*/
function GetIndexes($Mode = 'RO')
{
if ($Mode == 'RW') {
// Read-write, can only be sent to RW servers!
foreach($this->AllIndexes AS $id) {
if ($this->Options[$id]['Mode'] == 'RW') $Indexes2Use[] = $id;
}
} else {
// All servers may be used
$Indexes2Use = $this->AllIndexes;
}
return $Indexes2Use;
}
/**
* Get index 2 use using BalanceMethod
*
* @param array $Indexes2Use Array of indexes to use in balancing
* @param string $BalanceMethod Method to use in balancing (Random, RandomFixed (default), RoundRobin, Load, Weighted)
*/
function GetIndex2Use($Indexes2Use, $BalanceMethod = 'RandomFixed')
{
if (count($Indexes2Use) > 1) {
// Index not set, select connection handler for query
// BalanceMethod: Random, RandomFixed (default), RoundRobin, Load, Weighted
// Types: Slave, Master, MasterAndSlave (writes will always be sent to a master)
switch($BalanceMethod) {
case 'Backup':
foreach($Indexes2Use AS $id) {
if ($this->CheckServer($id)) {
$Index = $id;
break;
}
}
break;
case 'Random':
for($i = 0; $i <= count($Indexes2Use); $i++) {
mt_srand();
$id = $Indexes2Use[(int)mt_rand(0, count($Indexes2Use) - 1)];
if ($this->CheckServer($id)) {
$Index = $id;
break;
} else {
// pop Indexes2Use
$newIndexes2Use = Array();
foreach($Indexes2Use AS $id) {
if ($id != $Index) {
$newIndexes2Use[] = $id;
}
}
$Indexes2Use = $newIndexes2Use;
}
}
break;
case 'Load': // Not implemented
case 'Weighted': // Weighted Round Robin
case 'WeightedRoundRobin': // Not implemented
case 'RoundRobin':
for($i = 0; $i <= count($Indexes2Use); $i++) {
$idt = (int)(($this->LastIndex >= count($Indexes2Use) - 1) ? 0 : $this->LastIndex + 1);
$id = $Indexes2Use[$idt];
if ($this->CheckServer($id)) {
$Index = $id;
break;
} else {
$this->LastIndex++;
}
}
$this->LastIndex = $idt;
break;
case 'RandomFixed':
default:
if ($this->LastIndex) {
$Index = $this->LastIndex;
} else {
for($i = 0; $i <= count($Indexes2Use); $i++) {
mt_srand();
$id = $Indexes2Use[(int)mt_rand(0, count($Indexes2Use) - 1)];
if ($this->CheckServer($id)) {
$Index = $id;
break;
} else {
// pop Indexes2Use
$newIndexes2Use = Array();
foreach($Indexes2Use AS $idt) {
if ($id != $idt) {
$newIndexes2Use[] = $id;
}
}
$Indexes2Use = $newIndexes2Use;
}
}
}
break;
}
} elseif (count($Indexes2Use) == 1) {
$Index = $Indexes2Use[0];
if (!$this->CheckServer($Index)) {
DB::RaiseFatalError($this);
}
} else {
$Index = FALSE;
}
return $Index;
}
/**
* Check server connection and remove from AllIndexes if down
*
* @param int $Index Index of server to check
* @return boolean True on server OK, False on server down
* @access public
*/
function CheckServer($Index)
{
if ($this->ConnectionID[$Index] == -1) $this->Connect($Index);
if ($this->DBHandler[$Index]->ConnectionID) {
$RV = TRUE;
} else {
// Server seems down remove from AllIndexes!
$this->RemoveHandler($Index);
$RV = FALSE;
}
return $RV;
}
/**
* Raise database/query error
*
* This function looks in the ini file for the "nitrodb_email_on_query_error" and
* "log_on_query_error" parameters in the Debug section and emails to those
* addresses, respectively, logs to the given file, the error.
*
* <CODE>
* if ($SomethingWentWrong) {
* DB::Error($DB['Nitro']);
* }
*
* if ($SomethingWentWrong) {
* $$DB['Nitro']->Error();
* }
* </CODE>
*
* @param object $link Database object
*/
function Error($link = FALSE)
{
global $__NitroConf;
if (is_object($__NitroConf) && $__NitroConf->CONF && ($__NitroConf->CONF['Debug']['nitrodb_email_query_error'] OR $__NitroConf->CONF['Debug']['nitrodb_log_query_error'])) {
if (!$link) $link =& $this;
$error = '';
if ($link->Error) $error.= "ERROR: ".$link->Error."\n";
if (is_array($link->DBHandler)) {
foreach($link->DBHandler AS $index => $handler) {
if (DB::isDBHandler($handler)) {
$error.= "Handler ($index): ".$link->DBHandler[0]->Error."\n";
}
}
} else {
$error.= "No handler found\n";
}
if ($error) {
if ($__NitroConf->CONF['Debug']['nitrodb_email_query_error']) {
$recipients = $__NitroConf->CONF['Debug']['nitrodb_email_query_error'];
$subject = "Database error on ".$this->DSN;
$body = $error;
@mail($recipients, $subject, $body);
}
if ($__NitroConf->CONF['Debug']['nitrodb_log_query_error']) {
if ($fp = @fopen($this->Conf['Debug']['nitrodb_log_query_error'], 'w')) {
fwrite($fp, $error, strlen($error));
fclose($fp);
}
}
}
}
}
/**
* Raise fatal database error
*
* This function searches for the database error files, displays them and
* exits. First it looks for a file called DBError.php in document root, then
* for a file called DBError.html in document root and lastly for DBError.html
* in the defaults/text directory of Nitro. If none of these exist, it will echo
* the error directly.
*
* <CODE>
* if ($SomethingWentWrong) {
* DB::RaiseFatalError($DB['Nitro']);
* }
* </CODE>
*
* @param object $link Database object
*/
function RaiseFatalError($link)
{
if (file_exists($_SERVER["DOCUMENT_ROOT"]."/DBError.php")) {
/* Include error page /DBError.php if error found and file exists */
include $_SERVER["DOCUMENT_ROOT"]."/DBError.php";
} elseif (file_exists($_SERVER["DOCUMENT_ROOT"]."/DBError.html")) {
/* Include error page /DBError.html if error found and file exists */
include $_SERVER["DOCUMENT_ROOT"]."/DBError.html";
} elseif (file_exists(NITRO_PATH."/Defaults/Texts/DBError.html")) {
/* Include error page Defaults/Texts/DBError.html if error found and file exists */
include NITRO_PATH."Defaults/Texts/DBError.html";
} else {
echo "ERROR: ".$link->Error;
if (is_array($link->DBHandler)) {
foreach($link->DBHandler AS $index => $handler) {
if (DB::isDBHandler($handler)) {
echo " (Handler ($index): ".$handler->Error.")";
}
}
} else {
echo "No handler found";
}
}
exit;
}
}
?>