Merge branch 'extended-cookie'
[scuttle] / includes / db / mssql-odbc.php
1 <?php
2 /** 
3 *
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 
8 *
9 */
10
11 /**
12 * @ignore
13 */
14 if (!defined('SQL_LAYER'))
15 {
16
17 define('SQL_LAYER', 'mssql-odbc');
18
19 /**
20 * @package dbal_odbc_mssql
21 * MSSQL ODBC Database Abstraction Layer for MSSQL
22 * Minimum Requirement is Version 2000+
23 */
24 class sql_db
25 {
26         var $db_connect_id;
27         var $query_result;
28         var $return_on_error = false;
29         var $transaction = false;
30         var $sql_time = 0;
31         var $num_queries = 0;
32         var $open_queries = array();
33
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();
39
40         function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false)
41         {
42                 $this->persistency = $persistency;
43                 $this->user = $sqluser;
44                 $this->server = $sqlserver . (($port) ? ':' . $port : '');
45                 $this->dbname = $database;
46
47                 $this->db_connect_id = ($this->persistency) ? @odbc_pconnect($this->server, $this->user, $sqlpassword) : @odbc_connect($this->server, $this->user, $sqlpassword);
48
49                 return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
50         }
51
52         //
53         // Other base methods
54         //
55         function sql_close()
56         {
57                 if (!$this->db_connect_id)
58                 {
59                         return false;
60                 }
61
62                 if ($this->transaction)
63                 {
64                         @odbc_commit($this->db_connect_id);
65                 }
66
67                 if (sizeof($this->result_rowset))
68                 {
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);
74                 }
75
76                 if (sizeof($this->open_queries))
77                 {
78                         foreach ($this->open_queries as $i_query_id => $query_id)
79                         {
80                                 @odbc_free_result($query_id);
81                         }
82                 }
83
84                 return @odbc_close($this->db_connect_id);
85         }
86
87         function sql_return_on_error($fail = false)
88         {
89                 $this->return_on_error = $fail;
90         }
91
92         function sql_num_queries()
93         {
94                 return $this->num_queries;
95         }
96
97         function sql_transaction($status = 'begin')
98         {
99                 switch ($status)
100                 {
101                         case 'begin':
102                                 $result = @odbc_autocommit($this->db_connect_id, false);
103                                 $this->transaction = true;
104                                 break;
105
106                         case 'commit':
107                                 $result = @odbc_commit($this->db_connect_id);
108                                 @odbc_autocommit($this->db_connect_id, true);
109                                 $this->transaction = false;
110
111                                 if (!$result)
112                                 {
113                                         @odbc_rollback($this->db_connect_id);
114                                         @odbc_autocommit($this->db_connect_id, true);
115                                 }
116                                 break;
117
118                         case 'rollback':
119                                 $result = @odbc_rollback($this->db_connect_id);
120                                 @odbc_autocommit($this->db_connect_id, true);
121                                 $this->transaction = false;
122                                 break;
123
124                         default:
125                                 $result = true;
126                 }
127
128                 return $result;
129         }
130
131         // Base query method
132         function sql_query($query = '', $cache_ttl = 0)
133         {
134                 if ($query != '')
135                 {
136                         global $cache;
137
138                         // EXPLAIN only in extra debug mode
139                         if (defined('DEBUG_EXTRA'))
140                         {
141                                 $this->sql_report('start', $query);
142                         }
143
144                         $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false;
145
146                         if (!$this->query_result)
147                         {
148                                 $this->num_queries++;
149
150                                 if (($this->query_result = $this->_odbc_execute_query($query)) === false)
151                                 {
152                                         $this->sql_error($query);
153                                 }
154
155                                 if (defined('DEBUG_EXTRA'))
156                                 {
157                                         $this->sql_report('stop', $query);
158                                 }
159
160                                 if ($cache_ttl && method_exists($cache, 'sql_save'))
161                                 {
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()
165                                 }
166                                 else if (strpos($query, 'SELECT') !== false && $this->query_result)
167                                 {
168                                         $this->open_queries[(int) $this->query_result] = $this->query_result;
169                                 }
170                         }
171                         else if (defined('DEBUG_EXTRA'))
172                         {
173                                 $this->sql_report('fromcache', $query);
174                         }
175                 }
176                 else
177                 {
178                         return false;
179                 }
180
181                 return ($this->query_result) ? $this->query_result : false;
182         }
183
184         function _odbc_execute_query($query)
185         {
186                 $result = false;
187                 
188                 if (eregi("^SELECT ", $query))
189                 {
190                         $result = @odbc_exec($this->db_connect_id, $query); 
191
192                         if ($result)
193                         {
194                                 if (empty($this->field_names[$result]))
195                                 {
196                                         for ($i = 1, $j = @odbc_num_fields($result) + 1; $i < $j; $i++)
197                                         {
198                                                 $this->field_names[$result][] = @odbc_field_name($result, $i);
199                                                 $this->field_types[$result][] = @odbc_field_type($result, $i);
200                                         }
201                                 }
202
203                                 $this->current_row[$result] = 0;
204                                 $this->result_rowset[$result] = array();
205
206                                 $row_outer = (isset($row_offset)) ? $row_offset + 1 : 1;
207                                 $row_outer_max = (isset($num_rows)) ? $row_offset + $num_rows + 1 : 1E9;
208                                 $row_inner = 0;
209
210                                 while (@odbc_fetch_row($result, $row_outer) && $row_outer < $row_outer_max)
211                                 {
212                                         for ($i = 0, $j = sizeof($this->field_names[$result]); $i < $j; $i++)
213                                         {
214                                                 $this->result_rowset[$result][$row_inner][$this->field_names[$result][$i]] = stripslashes(@odbc_result($result, $i + 1));
215                                         }
216
217                                         $row_outer++;
218                                         $row_inner++;
219                                 }
220
221                                 $this->num_rows[$result] = sizeof($this->result_rowset[$result]);       
222                         }
223                 }
224                 else if (eregi("^INSERT ", $query))
225                 {
226                         $result = @odbc_exec($this->db_connect_id, $query);
227
228                         if ($result)
229                         {
230                                 $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY');
231                                 if ($result_id)
232                                 {
233                                         if (@odbc_fetch_row($result_id))
234                                         {
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);
237                                         }
238                                 }
239                         }
240                 }
241                 else
242                 {
243                         $result = @odbc_exec($this->db_connect_id, $query);
244
245                         if ($result)
246                         {
247                                 $this->affected_rows[$this->db_connect_id] = @odbc_num_rows($result);
248                         }
249                 }
250
251                 return $result;
252         }
253
254         function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) 
255         { 
256                 if ($query != '') 
257                 {
258                         $this->query_result = false; 
259
260                         // if $total is set to 0 we do not want to limit the number of rows
261                         if ($total == 0)
262                         {
263                                 $total = -1;
264                         }
265
266                         $row_offset = ($total) ? $offset : '';
267                         $num_rows = ($total) ? $total : $offset;
268
269                         $query = 'SELECT TOP ' . ($row_offset + $num_rows) . ' ' . substr($query, 6);
270
271                         return $this->sql_query($query, $cache_ttl); 
272                 } 
273                 else 
274                 { 
275                         return false; 
276                 } 
277         }
278
279         // Idea for this from Ikonboard
280         function sql_build_array($query, $assoc_ary = false)
281         {
282                 if (!is_array($assoc_ary))
283                 {
284                         return false;
285                 }
286
287                 $fields = array();
288                 $values = array();
289                 if ($query == 'INSERT')
290                 {
291                         foreach ($assoc_ary as $key => $var)
292                         {
293                                 $fields[] = $key;
294
295                                 if (is_null($var))
296                                 {
297                                         $values[] = 'NULL';
298                                 }
299                                 elseif (is_string($var))
300                                 {
301                                         $values[] = "'" . $this->sql_escape($var) . "'";
302                                 }
303                                 else
304                                 {
305                                         $values[] = (is_bool($var)) ? intval($var) : $var;
306                                 }
307                         }
308
309                         $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
310                 }
311                 else if ($query == 'UPDATE' || $query == 'SELECT')
312                 {
313                         $values = array();
314                         foreach ($assoc_ary as $key => $var)
315                         {
316                                 if (is_null($var))
317                                 {
318                                         $values[] = "$key = NULL";
319                                 }
320                                 elseif (is_string($var))
321                                 {
322                                         $values[] = "$key = '" . $this->sql_escape($var) . "'";
323                                 }
324                                 else
325                                 {
326                                         $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var";
327                                 }
328                         }
329                         $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values);
330                 }
331
332                 return $query;
333         }
334
335         // Other query methods
336         //
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)
340         {
341                 if (!$query_id)
342                 {
343                         $query_id = $this->query_result;
344                 }
345
346                 return ($query_id) ? @$this->num_rows($query_id) : false;
347         }
348
349         function sql_affectedrows()
350         {
351                 return ($this->affected_rows[$this->db_connect_id]) ? $this->affected_rows[$this->db_connect_id] : false;
352         }
353
354         function sql_fetchrow($query_id = false)
355         {
356                 global $cache;
357
358                 if (!$query_id)
359                 {
360                         $query_id = $this->query_result;
361                 }
362
363                 if (isset($cache->sql_rowset[$query_id]))
364                 {
365                         return $cache->sql_fetchrow($query_id);
366                 }
367
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;
369         }
370
371         function sql_fetchrowset($query_id = false)
372         {
373                 if (!$query_id)
374                 {
375                         $query_id = $this->query_result;
376                 }
377
378                 return ($this->num_rows[$query_id]) ? $this->result_rowset[$query_id] : false;
379         }
380
381         function sql_fetchfield($field, $rownum = -1, $query_id = false)
382         {
383                 if (!$query_id)
384                 {
385                         $query_id = $this->query_result;
386                 }
387
388                 if ($query_id)
389                 {
390                         if ($rownum < $this->num_rows[$query_id])
391                         {
392                                 $getrow = ($rownum == -1) ? $this->current_row[$query_id] - 1 : $rownum;
393
394                                 return $this->result_rowset[$query_id][$getrow][$this->field_names[$query_id][$field]];
395                         }
396                 }
397
398                 return false;
399         }
400
401         function sql_rowseek($rownum, $query_id = false)
402         {
403                 if (!$query_id)
404                 {
405                         $query_id = $this->query_result;
406                 }
407
408                 if (isset($this->current_row[$query_id]))
409                 {
410                         $this->current_row[$query_id] = $rownum;
411                         return true;
412                 }
413
414                 return false;
415         }
416
417         function sql_nextid()
418         {
419                 return ($this->next_id[$this->db_connect_id]) ? $this->next_id[$this->db_connect_id] : false;
420         }
421
422         function sql_freeresult($query_id = false)
423         {
424                 if (!$query_id)
425                 {
426                         $query_id = $this->query_result;
427                 }
428
429                 if (isset($this->open_queries[(int) $query_id]))
430                 {
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]);
437
438                         return @odbc_free_result($query_id);
439                 }
440
441                 return false;
442         }
443
444         function sql_escape($msg)
445         {
446                 return str_replace("'", "''", str_replace('\\', '\\\\', $msg));
447         }
448
449         function sql_error($sql = '')
450         {
451                 if (!$this->return_on_error)
452                 {
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'] : ''));
455
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 />';
457
458                         if ($this->transaction)
459                         {
460                                 $this->sql_transaction('rollback');
461                         }
462                         
463                         trigger_error($message, E_USER_ERROR);
464                 }
465
466                 $result = array(
467                         'message'       => @odbc_errormsg(),
468                         'code'          => @odbc_error()
469                 );
470
471                 return $result;
472         }
473
474         function sql_report($mode, $query = '')
475         {
476                 if (empty($_GET['explain']))
477                 {
478                         return;
479                 }
480
481                 global $cache, $starttime, $phpbb_root_path;
482                 static $curtime, $query_hold, $html_hold;
483                 static $sql_report = '';
484                 static $cache_num_queries = 0;
485
486                 if (!$query && !empty($query_hold))
487                 {
488                         $query = $query_hold;
489                 }
490
491                 switch ($mode)
492                 {
493                         case 'display':
494                                 if (!empty($cache))
495                                 {
496                                         $cache->unload();
497                                 }
498                                 $this->sql_close();
499
500                                 $mtime = explode(' ', microtime());
501                                 $totaltime = $mtime[0] + $mtime[1] - $starttime;
502
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> &nbsp; &nbsp; &nbsp;</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>';
508                                 echo $sql_report;
509                                 echo '</td></tr></table><br /></body></html>';
510                                 exit;
511                                 break;
512
513                         case 'start':
514                                 $query_hold = $query;
515                                 $html_hold = '';
516
517                                 $curtime = explode(' ', microtime());
518                                 $curtime = $curtime[0] + $curtime[1];
519                                 break;
520
521                         case 'fromcache':
522                                 $endtime = explode(' ', microtime());
523                                 $endtime = $endtime[0] + $endtime[1];
524
525                                 $result = $this->_odbc_execute_query($query);
526
527                                 $splittime = explode(' ', microtime());
528                                 $splittime = $splittime[0] + $splittime[1];
529
530                                 $time_cache = $endtime - $curtime;
531                                 $time_db = $splittime - $endtime;
532                                 $color = ($time_db > $time_cache) ? 'green' : 'red';
533
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">';
535
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>';
537
538                                 // Pad the start time to not interfere with page timing
539                                 $starttime += $time_db;
540
541                                 @odbc_free_result($result);
542                                 $cache_num_queries++;
543                                 break;
544
545                         case 'stop':
546                                 $endtime = explode(' ', microtime());
547                                 $endtime = $endtime[0] + $endtime[1];
548
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">';
550
551                                 if ($this->query_result)
552                                 {
553                                         if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query))
554                                         {
555                                                 $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | ';
556                                         }
557                                         $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>';
558                                 }
559                                 else
560                                 {
561                                         $error = $this->sql_error();
562                                         $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']);
563                                 }
564
565                                 $sql_report .= '</p>';
566
567                                 $this->sql_time += $endtime - $curtime;
568                                 break;
569                 }
570         }
571
572 } // class sql_db
573
574 } // if ... define
575
576 ?>

Benjamin Mako Hill || Want to submit a patch?