mirror of
https://github.com/phpbb/phpbb.git
synced 2025-06-11 00:40:56 +02:00
build
git-tools
phpBB
adm
cache
develop
docs
download
files
images
includes
acm
acp
auth
captcha
db
db_tools.php
dbal.php
firebird.php
index.htm
mssql.php
mssql_odbc.php
mssqlnative.php
mysql.php
mysqli.php
oracle.php
postgres.php
sqlite.php
diff
hooks
mcp
questionnaire
search
ucp
utf
.htaccess
auth.php
bbcode.php
cache.php
constants.php
error_collector.php
functions.php
functions_admin.php
functions_compress.php
functions_content.php
functions_convert.php
functions_database_helper.php
functions_display.php
functions_install.php
functions_jabber.php
functions_messenger.php
functions_module.php
functions_posting.php
functions_privmsgs.php
functions_profile_fields.php
functions_template.php
functions_transfer.php
functions_upload.php
functions_user.php
index.htm
message_parser.php
session.php
startup.php
template.php
install
language
store
styles
.htaccess
common.php
composer.json
composer.lock
cron.php
faq.php
feed.php
index.php
mcp.php
memberlist.php
posting.php
report.php
search.php
style.php
ucp.php
viewforum.php
viewonline.php
viewtopic.php
web.config
tests
travis
.gitignore
.travis.yml
README.md
composer.phar
phpunit.xml.all
phpunit.xml.dist
phpunit.xml.functional
1000 lines
25 KiB
PHP
1000 lines
25 KiB
PHP
<?php
|
|
/**
|
|
*
|
|
* @package dbal
|
|
* @version $Id$
|
|
* @copyright (c) 2005 phpBB Group
|
|
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
if (!defined('IN_PHPBB'))
|
|
{
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Database Abstraction Layer
|
|
* @package dbal
|
|
*/
|
|
class dbal
|
|
{
|
|
var $db_connect_id;
|
|
var $query_result;
|
|
var $return_on_error = false;
|
|
var $transaction = false;
|
|
var $sql_time = 0;
|
|
var $num_queries = array();
|
|
var $open_queries = array();
|
|
|
|
var $curtime = 0;
|
|
var $query_hold = '';
|
|
var $html_hold = '';
|
|
var $sql_report = '';
|
|
|
|
var $persistency = false;
|
|
var $user = '';
|
|
var $server = '';
|
|
var $dbname = '';
|
|
|
|
// Set to true if error triggered
|
|
var $sql_error_triggered = false;
|
|
|
|
// Holding the last sql query on sql error
|
|
var $sql_error_sql = '';
|
|
// Holding the error information - only populated if sql_error_triggered is set
|
|
var $sql_error_returned = array();
|
|
|
|
// Holding transaction count
|
|
var $transactions = 0;
|
|
|
|
// Supports multi inserts?
|
|
var $multi_insert = false;
|
|
|
|
/**
|
|
* Current sql layer
|
|
*/
|
|
var $sql_layer = '';
|
|
|
|
/**
|
|
* Wildcards for matching any (%) or exactly one (_) character within LIKE expressions
|
|
*/
|
|
var $any_char;
|
|
var $one_char;
|
|
|
|
/**
|
|
* Exact version of the DBAL, directly queried
|
|
*/
|
|
var $sql_server_version = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
function dbal()
|
|
{
|
|
$this->num_queries = array(
|
|
'cached' => 0,
|
|
'normal' => 0,
|
|
'total' => 0,
|
|
);
|
|
|
|
// Fill default sql layer based on the class being called.
|
|
// This can be changed by the specified layer itself later if needed.
|
|
$this->sql_layer = substr(get_class($this), 5);
|
|
|
|
// Do not change this please! This variable is used to easy the use of it - and is hardcoded.
|
|
$this->any_char = chr(0) . '%';
|
|
$this->one_char = chr(0) . '_';
|
|
}
|
|
|
|
/**
|
|
* return on error or display error message
|
|
*/
|
|
function sql_return_on_error($fail = false)
|
|
{
|
|
$this->sql_error_triggered = false;
|
|
$this->sql_error_sql = '';
|
|
|
|
$this->return_on_error = $fail;
|
|
}
|
|
|
|
/**
|
|
* Return number of sql queries and cached sql queries used
|
|
*/
|
|
function sql_num_queries($cached = false)
|
|
{
|
|
return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal'];
|
|
}
|
|
|
|
/**
|
|
* Add to query count
|
|
*/
|
|
function sql_add_num_queries($cached = false)
|
|
{
|
|
$this->num_queries['cached'] += ($cached !== false) ? 1 : 0;
|
|
$this->num_queries['normal'] += ($cached !== false) ? 0 : 1;
|
|
$this->num_queries['total'] += 1;
|
|
}
|
|
|
|
/**
|
|
* DBAL garbage collection, close sql connection
|
|
*/
|
|
function sql_close()
|
|
{
|
|
if (!$this->db_connect_id)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ($this->transaction)
|
|
{
|
|
do
|
|
{
|
|
$this->sql_transaction('commit');
|
|
}
|
|
while ($this->transaction);
|
|
}
|
|
|
|
foreach ($this->open_queries as $query_id)
|
|
{
|
|
$this->sql_freeresult($query_id);
|
|
}
|
|
|
|
// Connection closed correctly. Set db_connect_id to false to prevent errors
|
|
if ($result = $this->_sql_close())
|
|
{
|
|
$this->db_connect_id = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Build LIMIT query
|
|
* Doing some validation here.
|
|
*/
|
|
function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
|
|
{
|
|
if (empty($query))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Never use a negative total or offset
|
|
$total = ($total < 0) ? 0 : $total;
|
|
$offset = ($offset < 0) ? 0 : $offset;
|
|
|
|
return $this->_sql_query_limit($query, $total, $offset, $cache_ttl);
|
|
}
|
|
|
|
/**
|
|
* Fetch all rows
|
|
*/
|
|
function sql_fetchrowset($query_id = false)
|
|
{
|
|
if ($query_id === false)
|
|
{
|
|
$query_id = $this->query_result;
|
|
}
|
|
|
|
if ($query_id !== false)
|
|
{
|
|
$result = array();
|
|
while ($row = $this->sql_fetchrow($query_id))
|
|
{
|
|
$result[] = $row;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Seek to given row number
|
|
* rownum is zero-based
|
|
*/
|
|
function sql_rowseek($rownum, &$query_id)
|
|
{
|
|
global $cache;
|
|
|
|
if ($query_id === false)
|
|
{
|
|
$query_id = $this->query_result;
|
|
}
|
|
|
|
if (isset($cache->sql_rowset[$query_id]))
|
|
{
|
|
return $cache->sql_rowseek($rownum, $query_id);
|
|
}
|
|
|
|
if ($query_id === false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$this->sql_freeresult($query_id);
|
|
$query_id = $this->sql_query($this->last_query_text);
|
|
|
|
if ($query_id === false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We do not fetch the row for rownum == 0 because then the next resultset would be the second row
|
|
for ($i = 0; $i < $rownum; $i++)
|
|
{
|
|
if (!$this->sql_fetchrow($query_id))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fetch field
|
|
* if rownum is false, the current row is used, else it is pointing to the row (zero-based)
|
|
*/
|
|
function sql_fetchfield($field, $rownum = false, $query_id = false)
|
|
{
|
|
global $cache;
|
|
|
|
if ($query_id === false)
|
|
{
|
|
$query_id = $this->query_result;
|
|
}
|
|
|
|
if ($query_id !== false)
|
|
{
|
|
if ($rownum !== false)
|
|
{
|
|
$this->sql_rowseek($rownum, $query_id);
|
|
}
|
|
|
|
if (!is_object($query_id) && isset($cache->sql_rowset[$query_id]))
|
|
{
|
|
return $cache->sql_fetchfield($query_id, $field);
|
|
}
|
|
|
|
$row = $this->sql_fetchrow($query_id);
|
|
return (isset($row[$field])) ? $row[$field] : false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Correctly adjust LIKE expression for special characters
|
|
* Some DBMS are handling them in a different way
|
|
*
|
|
* @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char
|
|
* @return string LIKE expression including the keyword!
|
|
*/
|
|
function sql_like_expression($expression)
|
|
{
|
|
$expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression);
|
|
$expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);
|
|
|
|
return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\'');
|
|
}
|
|
|
|
/**
|
|
* Returns whether results of a query need to be buffered to run a transaction while iterating over them.
|
|
*
|
|
* @return bool Whether buffering is required.
|
|
*/
|
|
function sql_buffer_nested_transactions()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* SQL Transaction
|
|
* @access private
|
|
*/
|
|
function sql_transaction($status = 'begin')
|
|
{
|
|
switch ($status)
|
|
{
|
|
case 'begin':
|
|
// If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit)
|
|
if ($this->transaction)
|
|
{
|
|
$this->transactions++;
|
|
return true;
|
|
}
|
|
|
|
$result = $this->_sql_transaction('begin');
|
|
|
|
if (!$result)
|
|
{
|
|
$this->sql_error();
|
|
}
|
|
|
|
$this->transaction = true;
|
|
break;
|
|
|
|
case 'commit':
|
|
// If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions
|
|
if ($this->transaction && $this->transactions)
|
|
{
|
|
$this->transactions--;
|
|
return true;
|
|
}
|
|
|
|
// Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled)
|
|
// This implies we have transaction always set for autocommit db's
|
|
if (!$this->transaction)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$result = $this->_sql_transaction('commit');
|
|
|
|
if (!$result)
|
|
{
|
|
$this->sql_error();
|
|
}
|
|
|
|
$this->transaction = false;
|
|
$this->transactions = 0;
|
|
break;
|
|
|
|
case 'rollback':
|
|
$result = $this->_sql_transaction('rollback');
|
|
$this->transaction = false;
|
|
$this->transactions = 0;
|
|
break;
|
|
|
|
default:
|
|
$result = $this->_sql_transaction($status);
|
|
break;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Build sql statement from array for insert/update/select statements
|
|
*
|
|
* Idea for this from Ikonboard
|
|
* Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT
|
|
*
|
|
*/
|
|
function sql_build_array($query, $assoc_ary = false)
|
|
{
|
|
if (!is_array($assoc_ary))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$fields = $values = array();
|
|
|
|
if ($query == 'INSERT' || $query == 'INSERT_SELECT')
|
|
{
|
|
foreach ($assoc_ary as $key => $var)
|
|
{
|
|
$fields[] = $key;
|
|
|
|
if (is_array($var) && is_string($var[0]))
|
|
{
|
|
// This is used for INSERT_SELECT(s)
|
|
$values[] = $var[0];
|
|
}
|
|
else
|
|
{
|
|
$values[] = $this->_sql_validate_value($var);
|
|
}
|
|
}
|
|
|
|
$query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' ';
|
|
}
|
|
else if ($query == 'MULTI_INSERT')
|
|
{
|
|
trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR);
|
|
}
|
|
else if ($query == 'UPDATE' || $query == 'SELECT')
|
|
{
|
|
$values = array();
|
|
foreach ($assoc_ary as $key => $var)
|
|
{
|
|
$values[] = "$key = " . $this->_sql_validate_value($var);
|
|
}
|
|
$query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Build IN or NOT IN sql comparison string, uses <> or = on single element
|
|
* arrays to improve comparison speed
|
|
*
|
|
* @access public
|
|
* @param string $field name of the sql column that shall be compared
|
|
* @param array $array array of values that are allowed (IN) or not allowed (NOT IN)
|
|
* @param bool $negate true for NOT IN (), false for IN () (default)
|
|
* @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false.
|
|
*/
|
|
function sql_in_set($field, $array, $negate = false, $allow_empty_set = false)
|
|
{
|
|
if (!sizeof($array))
|
|
{
|
|
if (!$allow_empty_set)
|
|
{
|
|
// Print the backtrace to help identifying the location of the problematic code
|
|
$this->sql_error('No values specified for SQL IN comparison');
|
|
}
|
|
else
|
|
{
|
|
// NOT IN () actually means everything so use a tautology
|
|
if ($negate)
|
|
{
|
|
return '1=1';
|
|
}
|
|
// IN () actually means nothing so use a contradiction
|
|
else
|
|
{
|
|
return '1=0';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_array($array))
|
|
{
|
|
$array = array($array);
|
|
}
|
|
|
|
if (sizeof($array) == 1)
|
|
{
|
|
@reset($array);
|
|
$var = current($array);
|
|
|
|
return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var);
|
|
}
|
|
else
|
|
{
|
|
return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run binary AND operator on DB column.
|
|
* Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}"
|
|
*
|
|
* @param string $column_name The column name to use
|
|
* @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29
|
|
* @param string $compare Any custom SQL code after the check (for example "= 0")
|
|
*/
|
|
function sql_bit_and($column_name, $bit, $compare = '')
|
|
{
|
|
if (method_exists($this, '_sql_bit_and'))
|
|
{
|
|
return $this->_sql_bit_and($column_name, $bit, $compare);
|
|
}
|
|
|
|
return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
|
|
}
|
|
|
|
/**
|
|
* Run binary OR operator on DB column.
|
|
* Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}"
|
|
*
|
|
* @param string $column_name The column name to use
|
|
* @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29
|
|
* @param string $compare Any custom SQL code after the check (for example "= 0")
|
|
*/
|
|
function sql_bit_or($column_name, $bit, $compare = '')
|
|
{
|
|
if (method_exists($this, '_sql_bit_or'))
|
|
{
|
|
return $this->_sql_bit_or($column_name, $bit, $compare);
|
|
}
|
|
|
|
return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : '');
|
|
}
|
|
|
|
/**
|
|
* Run LOWER() on DB column of type text (i.e. neither varchar nor char).
|
|
*
|
|
* @param string $column_name The column name to use
|
|
*
|
|
* @return string A SQL statement like "LOWER($column_name)"
|
|
*/
|
|
function sql_lower_text($column_name)
|
|
{
|
|
return "LOWER($column_name)";
|
|
}
|
|
|
|
/**
|
|
* Run more than one insert statement.
|
|
*
|
|
* @param string $table table name to run the statements on
|
|
* @param array &$sql_ary multi-dimensional array holding the statement data.
|
|
*
|
|
* @return bool false if no statements were executed.
|
|
* @access public
|
|
*/
|
|
function sql_multi_insert($table, &$sql_ary)
|
|
{
|
|
if (!sizeof($sql_ary))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ($this->multi_insert)
|
|
{
|
|
$ary = array();
|
|
foreach ($sql_ary as $id => $_sql_ary)
|
|
{
|
|
// If by accident the sql array is only one-dimensional we build a normal insert statement
|
|
if (!is_array($_sql_ary))
|
|
{
|
|
return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary));
|
|
}
|
|
|
|
$values = array();
|
|
foreach ($_sql_ary as $key => $var)
|
|
{
|
|
$values[] = $this->_sql_validate_value($var);
|
|
}
|
|
$ary[] = '(' . implode(', ', $values) . ')';
|
|
}
|
|
|
|
return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary));
|
|
}
|
|
else
|
|
{
|
|
foreach ($sql_ary as $ary)
|
|
{
|
|
if (!is_array($ary))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary));
|
|
|
|
if (!$result)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Function for validating values
|
|
* @access private
|
|
*/
|
|
function _sql_validate_value($var)
|
|
{
|
|
if (is_null($var))
|
|
{
|
|
return 'NULL';
|
|
}
|
|
else if (is_string($var))
|
|
{
|
|
return "'" . $this->sql_escape($var) . "'";
|
|
}
|
|
else
|
|
{
|
|
return (is_bool($var)) ? intval($var) : $var;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build sql statement from array for select and select distinct statements
|
|
*
|
|
* Possible query values: SELECT, SELECT_DISTINCT
|
|
*/
|
|
function sql_build_query($query, $array)
|
|
{
|
|
$sql = '';
|
|
switch ($query)
|
|
{
|
|
case 'SELECT':
|
|
case 'SELECT_DISTINCT';
|
|
|
|
$sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM ';
|
|
|
|
// Build table array. We also build an alias array for later checks.
|
|
$table_array = $aliases = array();
|
|
$used_multi_alias = false;
|
|
|
|
foreach ($array['FROM'] as $table_name => $alias)
|
|
{
|
|
if (is_array($alias))
|
|
{
|
|
$used_multi_alias = true;
|
|
|
|
foreach ($alias as $multi_alias)
|
|
{
|
|
$table_array[] = $table_name . ' ' . $multi_alias;
|
|
$aliases[] = $multi_alias;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$table_array[] = $table_name . ' ' . $alias;
|
|
$aliases[] = $alias;
|
|
}
|
|
}
|
|
|
|
// We run the following code to determine if we need to re-order the table array. ;)
|
|
// The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison.
|
|
// DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is.
|
|
if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false)
|
|
{
|
|
// Take first LEFT JOIN
|
|
$join = current($array['LEFT_JOIN']);
|
|
|
|
// Determine the table used there (even if there are more than one used, we only want to have one
|
|
preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches);
|
|
|
|
// If there is a first join match, we need to make sure the table order is correct
|
|
if (!empty($matches[1]))
|
|
{
|
|
$first_join_match = trim($matches[1]);
|
|
$table_array = $last = array();
|
|
|
|
foreach ($array['FROM'] as $table_name => $alias)
|
|
{
|
|
if (is_array($alias))
|
|
{
|
|
foreach ($alias as $multi_alias)
|
|
{
|
|
($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias;
|
|
}
|
|
}
|
|
|
|
$table_array = array_merge($table_array, $last);
|
|
}
|
|
}
|
|
|
|
$sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array));
|
|
|
|
if (!empty($array['LEFT_JOIN']))
|
|
{
|
|
foreach ($array['LEFT_JOIN'] as $join)
|
|
{
|
|
$sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')';
|
|
}
|
|
}
|
|
|
|
if (!empty($array['WHERE']))
|
|
{
|
|
$sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']);
|
|
}
|
|
|
|
if (!empty($array['GROUP_BY']))
|
|
{
|
|
$sql .= ' GROUP BY ' . $array['GROUP_BY'];
|
|
}
|
|
|
|
if (!empty($array['ORDER_BY']))
|
|
{
|
|
$sql .= ' ORDER BY ' . $array['ORDER_BY'];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return $sql;
|
|
}
|
|
|
|
/**
|
|
* display sql error page
|
|
*/
|
|
function sql_error($sql = '')
|
|
{
|
|
global $auth, $user, $config;
|
|
|
|
// Set var to retrieve errored status
|
|
$this->sql_error_triggered = true;
|
|
$this->sql_error_sql = $sql;
|
|
|
|
$this->sql_error_returned = $this->_sql_error();
|
|
|
|
if (!$this->return_on_error)
|
|
{
|
|
$message = 'SQL ERROR [ ' . $this->sql_layer . ' ]<br /><br />' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']';
|
|
|
|
// Show complete SQL error and path to administrators only
|
|
// Additionally show complete error on installation or if extended debug mode is enabled
|
|
// The DEBUG_EXTRA constant is for development only!
|
|
if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG_EXTRA'))
|
|
{
|
|
$message .= ($sql) ? '<br /><br />SQL<br /><br />' . htmlspecialchars($sql) : '';
|
|
}
|
|
else
|
|
{
|
|
// If error occurs in initiating the session we need to use a pre-defined language string
|
|
// This could happen if the connection could not be established for example (then we are not able to grab the default language)
|
|
if (!isset($user->lang['SQL_ERROR_OCCURRED']))
|
|
{
|
|
$message .= '<br /><br />An sql error occurred while fetching this page. Please contact an administrator if this problem persists.';
|
|
}
|
|
else
|
|
{
|
|
if (!empty($config['board_contact']))
|
|
{
|
|
$message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
|
|
}
|
|
else
|
|
{
|
|
$message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', '');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->transaction)
|
|
{
|
|
$this->sql_transaction('rollback');
|
|
}
|
|
|
|
if (strlen($message) > 1024)
|
|
{
|
|
// We need to define $msg_long_text here to circumvent text stripping.
|
|
global $msg_long_text;
|
|
$msg_long_text = $message;
|
|
|
|
trigger_error(false, E_USER_ERROR);
|
|
}
|
|
|
|
trigger_error($message, E_USER_ERROR);
|
|
}
|
|
|
|
if ($this->transaction)
|
|
{
|
|
$this->sql_transaction('rollback');
|
|
}
|
|
|
|
return $this->sql_error_returned;
|
|
}
|
|
|
|
/**
|
|
* Explain queries
|
|
*/
|
|
function sql_report($mode, $query = '')
|
|
{
|
|
global $cache, $starttime, $phpbb_root_path, $user;
|
|
|
|
if (empty($_REQUEST['explain']))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!$query && $this->query_hold != '')
|
|
{
|
|
$query = $this->query_hold;
|
|
}
|
|
|
|
switch ($mode)
|
|
{
|
|
case 'display':
|
|
if (!empty($cache))
|
|
{
|
|
$cache->unload();
|
|
}
|
|
$this->sql_close();
|
|
|
|
$mtime = explode(' ', microtime());
|
|
$totaltime = $mtime[0] + $mtime[1] - $starttime;
|
|
|
|
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
|
<meta http-equiv="Content-Style-Type" content="text/css" />
|
|
<meta http-equiv="imagetoolbar" content="no" />
|
|
<title>SQL Report</title>
|
|
<link href="' . $phpbb_root_path . 'adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" />
|
|
</head>
|
|
<body id="errorpage">
|
|
<div id="wrap">
|
|
<div id="page-header">
|
|
<a href="' . build_url('explain') . '">Return to previous page</a>
|
|
</div>
|
|
<div id="page-body">
|
|
<div id="acp">
|
|
<div class="panel">
|
|
<span class="corners-top"><span></span></span>
|
|
<div id="content">
|
|
<h1>SQL Report</h1>
|
|
<br />
|
|
<p><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></p>
|
|
|
|
<p>Time spent on ' . $this->sql_layer . ' queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></p>
|
|
|
|
<br /><br />
|
|
' . $this->sql_report . '
|
|
</div>
|
|
<span class="corners-bottom"><span></span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="page-footer">
|
|
Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
|
|
exit_handler();
|
|
|
|
break;
|
|
|
|
case 'stop':
|
|
$endtime = explode(' ', microtime());
|
|
$endtime = $endtime[0] + $endtime[1];
|
|
|
|
$this->sql_report .= '
|
|
|
|
<table cellspacing="1">
|
|
<thead>
|
|
<tr>
|
|
<th>Query #' . $this->num_queries['total'] . '</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
' . $this->html_hold . '
|
|
|
|
<p style="text-align: center;">
|
|
';
|
|
|
|
if ($this->query_result)
|
|
{
|
|
if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query))
|
|
{
|
|
$this->sql_report .= 'Affected rows: <b>' . $this->sql_affectedrows($this->query_result) . '</b> | ';
|
|
}
|
|
$this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $this->curtime) . 's</b>';
|
|
}
|
|
else
|
|
{
|
|
$error = $this->sql_error();
|
|
$this->sql_report .= '<b style="color: red">FAILED</b> - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']);
|
|
}
|
|
|
|
$this->sql_report .= '</p><br /><br />';
|
|
|
|
$this->sql_time += $endtime - $this->curtime;
|
|
break;
|
|
|
|
case 'start':
|
|
$this->query_hold = $query;
|
|
$this->html_hold = '';
|
|
|
|
$this->_sql_report($mode, $query);
|
|
|
|
$this->curtime = explode(' ', microtime());
|
|
$this->curtime = $this->curtime[0] + $this->curtime[1];
|
|
|
|
break;
|
|
|
|
case 'add_select_row':
|
|
|
|
$html_table = func_get_arg(2);
|
|
$row = func_get_arg(3);
|
|
|
|
if (!$html_table && sizeof($row))
|
|
{
|
|
$html_table = true;
|
|
$this->html_hold .= '<table cellspacing="1"><tr>';
|
|
|
|
foreach (array_keys($row) as $val)
|
|
{
|
|
$this->html_hold .= '<th>' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>';
|
|
}
|
|
$this->html_hold .= '</tr>';
|
|
}
|
|
$this->html_hold .= '<tr>';
|
|
|
|
$class = 'row1';
|
|
foreach (array_values($row) as $val)
|
|
{
|
|
$class = ($class == 'row1') ? 'row2' : 'row1';
|
|
$this->html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>';
|
|
}
|
|
$this->html_hold .= '</tr>';
|
|
|
|
return $html_table;
|
|
|
|
break;
|
|
|
|
case 'fromcache':
|
|
|
|
$this->_sql_report($mode, $query);
|
|
|
|
break;
|
|
|
|
case 'record_fromcache':
|
|
|
|
$endtime = func_get_arg(2);
|
|
$splittime = func_get_arg(3);
|
|
|
|
$time_cache = $endtime - $this->curtime;
|
|
$time_db = $splittime - $endtime;
|
|
$color = ($time_db > $time_cache) ? 'green' : 'red';
|
|
|
|
$this->sql_report .= '<table cellspacing="1"><thead><tr><th>Query results obtained from the cache</th></tr></thead><tbody><tr>';
|
|
$this->sql_report .= '<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></tbody></table>';
|
|
$this->sql_report .= '<p style="text-align: center;">';
|
|
$this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p><br /><br />';
|
|
|
|
// Pad the start time to not interfere with page timing
|
|
$starttime += $time_db;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$this->_sql_report($mode, $query);
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the estimated number of rows in a specified table.
|
|
*
|
|
* @param string $table_name Table name
|
|
*
|
|
* @return string Number of rows in $table_name.
|
|
* Prefixed with ~ if estimated (otherwise exact).
|
|
*
|
|
* @access public
|
|
*/
|
|
function get_estimated_row_count($table_name)
|
|
{
|
|
return $this->get_row_count($table_name);
|
|
}
|
|
|
|
/**
|
|
* Gets the exact number of rows in a specified table.
|
|
*
|
|
* @param string $table_name Table name
|
|
*
|
|
* @return string Exact number of rows in $table_name.
|
|
*
|
|
* @access public
|
|
*/
|
|
function get_row_count($table_name)
|
|
{
|
|
$sql = 'SELECT COUNT(*) AS rows_total
|
|
FROM ' . $this->sql_escape($table_name);
|
|
$result = $this->sql_query($sql);
|
|
$rows_total = $this->sql_fetchfield('rows_total');
|
|
$this->sql_freeresult($result);
|
|
|
|
return $rows_total;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This variable holds the class name to use later
|
|
*/
|
|
$sql_db = (!empty($dbms)) ? 'dbal_' . basename($dbms) : 'dbal';
|
|
|
|
?>
|