num_queries = array( 'cached' => 0, 'normal' => 0, 'total' => 0, ); } /** * return on error or display error message */ function sql_return_on_error($fail = false) { $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) ? 1 : 0; $this->num_queries['normal'] += ($cached) ? 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) { $this->sql_transaction('commit'); } if (sizeof($this->open_queries)) { foreach ($this->open_queries as $i_query_id => $query_id) { $this->sql_freeresult($query_id); } } return $this->_sql_close(); } /** * Fetch all rows */ function sql_fetchrowset($query_id = false) { if (!$query_id) { $query_id = $this->query_result; } if ($query_id) { $result = array(); while ($row = $this->sql_fetchrow($query_id)) { $result[] = $row; } return $result; } return false; } /** * SQL Transaction * @private */ function sql_transaction($status = 'begin') { switch ($status) { case 'begin': // Commit previously opened transaction before opening another transaction if ($this->transaction) { $this->_sql_transaction('commit'); } $result = $this->_sql_transaction('begin'); $this->transaction = true; break; case 'commit': $result = $this->_sql_transaction('commit'); $this->transaction = false; if (!$result) { $this->_sql_transaction('rollback'); } break; case 'rollback': $result = $this->_sql_transaction('rollback'); $this->transaction = false; 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, MULTI_INSERT, UPDATE, SELECT */ function sql_build_array($query, $assoc_ary = false) { if (!is_array($assoc_ary)) { return false; } $fields = array(); $values = array(); if ($query == 'INSERT' || $query == 'INSERT_SELECT') { foreach ($assoc_ary as $key => $var) { $fields[] = $key; if (is_null($var)) { $values[] = 'NULL'; } else if (is_string($var)) { $values[] = "'" . $this->sql_escape($var) . "'"; } else if (is_array($var) && is_string($var[0])) { // This is used for INSERT_SELECT(s) $values[] = $var[0]; } else { $values[] = (is_bool($var)) ? intval($var) : $var; } } $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; } else if ($query == 'MULTI_INSERT') { $ary = array(); foreach ($assoc_ary as $id => $sql_ary) { $values = array(); foreach ($sql_ary as $key => $var) { if (is_null($var)) { $values[] = 'NULL'; } else if (is_string($var)) { $values[] = "'" . $this->sql_escape($var) . "'"; } else { $values[] = (is_bool($var)) ? intval($var) : $var; } } $ary[] = '(' . implode(', ', $values) . ')'; } $query = ' (' . implode(', ', array_keys($assoc_ary[0])) . ') VALUES ' . implode(', ', $ary); } else if ($query == 'UPDATE' || $query == 'SELECT') { $values = array(); foreach ($assoc_ary as $key => $var) { if (is_null($var)) { $values[] = "$key = NULL"; } else if (is_string($var)) { $values[] = "$key = '" . $this->sql_escape($var) . "'"; } else { $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; } } $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); } return $query; } /** * 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 '; $table_array = array(); foreach ($array['FROM'] as $table_name => $alias) { $table_array[] = $table_name . ' ' . $alias; } $sql .= $this->_sql_custom_build('FROM', implode(', ', $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; $error = $this->_sql_error(); if (!$this->return_on_error) { $message = 'SQL ERROR [ ' . SQL_LAYER . ' ]

' . $error['message'] . ' [' . $error['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'))) { // Print out a nice backtrace... $backtrace = get_backtrace(); $message .= ($sql) ? '

SQL

' . $sql : ''; $message .= ($backtrace) ? '

BACKTRACE
' . $backtrace : ''; $message .= '
'; } 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 .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persist.'; } else { $message .= '

' . $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); } return $error; } /** * Explain queries */ function sql_report($mode, $query = '') { global $cache, $starttime, $phpbb_root_path, $user; if (empty($_GET['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 ' SQL Report

SQL Report


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' : '') . '

Time spent on ' . SQL_LAYER . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's



' . $this->sql_report . '
'; exit; break; case 'stop': $endtime = explode(' ', microtime()); $endtime = $endtime[0] + $endtime[1]; $this->sql_report .= '
Query #' . $this->num_queries['total'] . '
' . $this->html_hold . '

'; if ($this->query_result) { if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) { $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; } $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; } else { $error = $this->sql_error(); $this->sql_report .= 'FAILED - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); } $this->sql_report .= '



'; $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 .= ''; foreach (array_keys($row) as $val) { $this->html_hold .= ''; } $this->html_hold .= ''; } $this->html_hold .= ''; $class = 'row1'; foreach (array_values($row) as $val) { $class = ($class == 'row1') ? 'row2' : 'row1'; $this->html_hold .= ''; } $this->html_hold .= ''; 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 .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; $this->sql_report .= '
Query results obtained from the cache
'; $this->sql_report .= '

'; $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; // Pad the start time to not interfere with page timing $starttime += $time_db; break; default: $this->_sql_report($mode, $query); break; } return true; } } /** */ if (!defined('IN_PHPBB')) { exit; } /** * This variable holds the class name to use later */ $sql_db = 'dbal_' . $dbms; ?>