4 * @package dbal_odbc_mssql
5 * @version $Id: mssql-odbc.php,v 1.2 2005/06/10 08:52:03 devalley Exp $
6 * @copyright (c) 2005 phpBB Group
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
14 if (!defined('SQL_LAYER'))
17 define('SQL_LAYER', 'mssql-odbc');
20 * @package dbal_odbc_mssql
21 * MSSQL ODBC Database Abstraction Layer for MSSQL
22 * Minimum Requirement is Version 2000+
28 var $return_on_error = false;
29 var $transaction = false;
32 var $open_queries = array();
34 var $result_rowset = array();
35 var $field_names = array();
36 var $field_types = array();
37 var $num_rows = array();
38 var $current_row = array();
40 function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false)
42 $this->persistency = $persistency;
43 $this->user = $sqluser;
44 $this->server = $sqlserver . (($port) ? ':' . $port : '');
45 $this->dbname = $database;
47 $this->db_connect_id = ($this->persistency) ? @odbc_pconnect($this->server, $this->user, $sqlpassword) : @odbc_connect($this->server, $this->user, $sqlpassword);
49 return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
57 if (!$this->db_connect_id)
62 if ($this->transaction)
64 @odbc_commit($this->db_connect_id);
67 if (sizeof($this->result_rowset))
69 unset($this->result_rowset);
70 unset($this->field_names);
71 unset($this->field_types);
72 unset($this->num_rows);
73 unset($this->current_row);
76 if (sizeof($this->open_queries))
78 foreach ($this->open_queries as $i_query_id => $query_id)
80 @odbc_free_result($query_id);
84 return @odbc_close($this->db_connect_id);
87 function sql_return_on_error($fail = false)
89 $this->return_on_error = $fail;
92 function sql_num_queries()
94 return $this->num_queries;
97 function sql_transaction($status = 'begin')
102 $result = @odbc_autocommit($this->db_connect_id, false);
103 $this->transaction = true;
107 $result = @odbc_commit($this->db_connect_id);
108 @odbc_autocommit($this->db_connect_id, true);
109 $this->transaction = false;
113 @odbc_rollback($this->db_connect_id);
114 @odbc_autocommit($this->db_connect_id, true);
119 $result = @odbc_rollback($this->db_connect_id);
120 @odbc_autocommit($this->db_connect_id, true);
121 $this->transaction = false;
132 function sql_query($query = '', $cache_ttl = 0)
138 // EXPLAIN only in extra debug mode
139 if (defined('DEBUG_EXTRA'))
141 $this->sql_report('start', $query);
144 $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false;
146 if (!$this->query_result)
148 $this->num_queries++;
150 if (($this->query_result = $this->_odbc_execute_query($query)) === false)
152 $this->sql_error($query);
155 if (defined('DEBUG_EXTRA'))
157 $this->sql_report('stop', $query);
160 if ($cache_ttl && method_exists($cache, 'sql_save'))
162 $this->open_queries[(int) $this->query_result] = $this->query_result;
163 $cache->sql_save($query, $this->query_result, $cache_ttl);
164 // odbc_free_result called within sql_save()
166 else if (strpos($query, 'SELECT') !== false && $this->query_result)
168 $this->open_queries[(int) $this->query_result] = $this->query_result;
171 else if (defined('DEBUG_EXTRA'))
173 $this->sql_report('fromcache', $query);
181 return ($this->query_result) ? $this->query_result : false;
184 function _odbc_execute_query($query)
188 if (eregi("^SELECT ", $query))
190 $result = @odbc_exec($this->db_connect_id, $query);
194 if (empty($this->field_names[$result]))
196 for ($i = 1, $j = @odbc_num_fields($result) + 1; $i < $j; $i++)
198 $this->field_names[$result][] = @odbc_field_name($result, $i);
199 $this->field_types[$result][] = @odbc_field_type($result, $i);
203 $this->current_row[$result] = 0;
204 $this->result_rowset[$result] = array();
206 $row_outer = (isset($row_offset)) ? $row_offset + 1 : 1;
207 $row_outer_max = (isset($num_rows)) ? $row_offset + $num_rows + 1 : 1E9;
210 while (@odbc_fetch_row($result, $row_outer) && $row_outer < $row_outer_max)
212 for ($i = 0, $j = sizeof($this->field_names[$result]); $i < $j; $i++)
214 $this->result_rowset[$result][$row_inner][$this->field_names[$result][$i]] = stripslashes(@odbc_result($result, $i + 1));
221 $this->num_rows[$result] = sizeof($this->result_rowset[$result]);
224 else if (eregi("^INSERT ", $query))
226 $result = @odbc_exec($this->db_connect_id, $query);
230 $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY');
233 if (@odbc_fetch_row($result_id))
235 $this->next_id[$this->db_connect_id] = @odbc_result($result_id, 1);
236 $this->affected_rows[$this->db_connect_id] = @odbc_num_rows($result);
243 $result = @odbc_exec($this->db_connect_id, $query);
247 $this->affected_rows[$this->db_connect_id] = @odbc_num_rows($result);
254 function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
258 $this->query_result = false;
260 // if $total is set to 0 we do not want to limit the number of rows
266 $row_offset = ($total) ? $offset : '';
267 $num_rows = ($total) ? $total : $offset;
269 $query = 'SELECT TOP ' . ($row_offset + $num_rows) . ' ' . substr($query, 6);
271 return $this->sql_query($query, $cache_ttl);
279 // Idea for this from Ikonboard
280 function sql_build_array($query, $assoc_ary = false)
282 if (!is_array($assoc_ary))
289 if ($query == 'INSERT')
291 foreach ($assoc_ary as $key => $var)
299 elseif (is_string($var))
301 $values[] = "'" . $this->sql_escape($var) . "'";
305 $values[] = (is_bool($var)) ? intval($var) : $var;
309 $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
311 else if ($query == 'UPDATE' || $query == 'SELECT')
314 foreach ($assoc_ary as $key => $var)
318 $values[] = "$key = NULL";
320 elseif (is_string($var))
322 $values[] = "$key = '" . $this->sql_escape($var) . "'";
326 $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var";
329 $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values);
335 // Other query methods
337 // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ...
338 // don't want this here by a middle Milestone
339 function sql_numrows($query_id = false)
343 $query_id = $this->query_result;
346 return ($query_id) ? @$this->num_rows($query_id) : false;
349 function sql_affectedrows()
351 return ($this->affected_rows[$this->db_connect_id]) ? $this->affected_rows[$this->db_connect_id] : false;
354 function sql_fetchrow($query_id = false)
360 $query_id = $this->query_result;
363 if (isset($cache->sql_rowset[$query_id]))
365 return $cache->sql_fetchrow($query_id);
368 return ($this->num_rows[$query_id] && $this->current_row[$query_id] < $this->num_rows[$query_id]) ? $this->result_rowset[$query_id][$this->current_row[$query_id]++] : false;
371 function sql_fetchrowset($query_id = false)
375 $query_id = $this->query_result;
378 return ($this->num_rows[$query_id]) ? $this->result_rowset[$query_id] : false;
381 function sql_fetchfield($field, $rownum = -1, $query_id = false)
385 $query_id = $this->query_result;
390 if ($rownum < $this->num_rows[$query_id])
392 $getrow = ($rownum == -1) ? $this->current_row[$query_id] - 1 : $rownum;
394 return $this->result_rowset[$query_id][$getrow][$this->field_names[$query_id][$field]];
401 function sql_rowseek($rownum, $query_id = false)
405 $query_id = $this->query_result;
408 if (isset($this->current_row[$query_id]))
410 $this->current_row[$query_id] = $rownum;
417 function sql_nextid()
419 return ($this->next_id[$this->db_connect_id]) ? $this->next_id[$this->db_connect_id] : false;
422 function sql_freeresult($query_id = false)
426 $query_id = $this->query_result;
429 if (isset($this->open_queries[(int) $query_id]))
431 unset($this->open_queries[(int) $query_id]);
432 unset($this->num_rows[$query_id]);
433 unset($this->current_row[$query_id]);
434 unset($this->result_rowset[$query_id]);
435 unset($this->field_names[$query_id]);
436 unset($this->field_types[$query_id]);
438 return @odbc_free_result($query_id);
444 function sql_escape($msg)
446 return str_replace("'", "''", str_replace('\\', '\\\\', $msg));
449 function sql_error($sql = '')
451 if (!$this->return_on_error)
453 $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF'];
454 $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : ''));
456 $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @odbc_errormsg() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />';
458 if ($this->transaction)
460 $this->sql_transaction('rollback');
463 trigger_error($message, E_USER_ERROR);
467 'message' => @odbc_errormsg(),
468 'code' => @odbc_error()
474 function sql_report($mode, $query = '')
476 if (empty($_GET['explain']))
481 global $cache, $starttime, $phpbb_root_path;
482 static $curtime, $query_hold, $html_hold;
483 static $sql_report = '';
484 static $cache_num_queries = 0;
486 if (!$query && !empty($query_hold))
488 $query = $query_hold;
500 $mtime = explode(' ', microtime());
501 $totaltime = $mtime[0] + $mtime[1] - $starttime;
503 echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n";
504 echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n";
505 echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n";
506 echo '</style><title>' . $msg_title . '</title></head><body>';
507 echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>';
509 echo '</td></tr></table><br /></body></html>';
514 $query_hold = $query;
517 $curtime = explode(' ', microtime());
518 $curtime = $curtime[0] + $curtime[1];
522 $endtime = explode(' ', microtime());
523 $endtime = $endtime[0] + $endtime[1];
525 $result = $this->_odbc_execute_query($query);
527 $splittime = explode(' ', microtime());
528 $splittime = $splittime[0] + $splittime[1];
530 $time_cache = $endtime - $curtime;
531 $time_db = $splittime - $endtime;
532 $color = ($time_db > $time_cache) ? 'green' : 'red';
534 $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">';
536 $sql_report .= 'Before: ' . sprintf('%.5f', $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>';
538 // Pad the start time to not interfere with page timing
539 $starttime += $time_db;
541 @odbc_free_result($result);
542 $cache_num_queries++;
546 $endtime = explode(' ', microtime());
547 $endtime = $endtime[0] + $endtime[1];
549 $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">';
551 if ($this->query_result)
553 if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query))
555 $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | ';
557 $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>';
561 $error = $this->sql_error();
562 $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']);
565 $sql_report .= '</p>';
567 $this->sql_time += $endtime - $curtime;