thomascube
2008-09-19 42e328a85f5880e3e767eebaaa88b55a2b49d2ff
commit | author | age
f45ec7 1 <?php
S 2
3 /*
4  +-----------------------------------------------------------------------+
47124c 5  | program/include/rcube_mdb2.php                                        |
f45ec7 6  |                                                                       |
S 7  | This file is part of the RoundCube Webmail client                     |
47124c 8  | Copyright (C) 2005-2008, RoundCube Dev. - Switzerland                 |
f45ec7 9  | Licensed under the GNU GPL                                            |
S 10  |                                                                       |
11  | PURPOSE:                                                              |
d2a9db 12  |   PEAR:DB wrapper class that implements PEAR MDB2 functions           |
T 13  |   See http://pear.php.net/package/MDB2                                |
f45ec7 14  |                                                                       |
S 15  +-----------------------------------------------------------------------+
d2a9db 16  | Author: Lukas Kahwe Smith <smith@pooteeweet.org>                      |
f45ec7 17  +-----------------------------------------------------------------------+
S 18
19  $Id$
20
21 */
22
d2a9db 23
T 24 /**
25  * Database independent query interface
26  *
27  * This is a wrapper for the PEAR::MDB2 class
28  *
6d969b 29  * @package    Database
d2a9db 30  * @author     David Saez Padros <david@ols.es>
T 31  * @author     Thomas Bruederli <roundcube@gmail.com>
32  * @author     Lukas Kahwe Smith <smith@pooteeweet.org>
33  * @version    1.16
34  * @link       http://pear.php.net/package/MDB2
35  */
6d969b 36 class rcube_mdb2
d2a9db 37   {
T 38   var $db_dsnw;               // DSN for write operations
39   var $db_dsnr;               // DSN for read operations
40   var $db_connected = false;  // Already connected ?
41   var $db_mode = '';          // Connection mode
42   var $db_handle = 0;         // Connection handle
43   var $db_error = false;
44   var $db_error_msg = '';
981472 45   var $debug_mode = false;
f45ec7 46
d2a9db 47   var $a_query_results = array('dummy');
T 48   var $last_res_id = 0;
f45ec7 49
d2a9db 50
T 51   /**
52    * Object constructor
53    *
54    * @param  string  DSN for read/write operations
55    * @param  string  Optional DSN for read only operations
56    */
bad419 57   function __construct($db_dsnw, $db_dsnr='', $pconn=false)
f45ec7 58     {
d2a9db 59     if ($db_dsnr=='')
T 60       $db_dsnr=$db_dsnw;
61
62     $this->db_dsnw = $db_dsnw;
63     $this->db_dsnr = $db_dsnr;
bad419 64     $this->db_pconn = $pconn;
T 65     
d2a9db 66     $dsn_array = MDB2::parseDSN($db_dsnw);
T 67     $this->db_provider = $dsn_array['phptype'];
f45ec7 68     }
S 69
d2a9db 70
T 71   /**
72    * Connect to specific database
73    *
74    * @param  string  DSN for DB connections
75    * @return object  PEAR database handle
76    * @access private
77    */
78   function dsn_connect($dsn)
f45ec7 79     {
d2a9db 80     // Use persistent connections if available
981472 81     $dbh = MDB2::connect($dsn, array(
T 82         'persistent' => $this->db_pconn,
0ce1a6 83         'emulate_prepared' => $this->debug_mode,
981472 84         'debug' => $this->debug_mode,
T 85         'debug_handler' => 'mdb2_debug_handler',
7eaf7a 86         'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL));
d2a9db 87
7eaf7a 88     if (MDB2::isError($dbh))
d2a9db 89       {
T 90       $this->db_error = TRUE;
91       $this->db_error_msg = $dbh->getMessage();
92       
7eaf7a 93       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__,
T 94         'file' => __FILE__, 'message' => $dbh->getUserInfo()), TRUE, FALSE);
d2a9db 95       }
T 96     else if ($this->db_provider=='sqlite')
97       {
98       $dsn_array = MDB2::parseDSN($dsn);
99       if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
100         $this->_sqlite_create_database($dbh, $this->sqlite_initials);
101       }
b66d40 102     else
T 103       $dbh->setCharset('utf8');
d2a9db 104
T 105     return $dbh;
106     }
107
108
109   /**
110    * Connect to appropiate databse
111    * depending on the operation
112    *
113    * @param  string  Connection mode (r|w)
114    * @access public
115    */
116   function db_connect($mode)
117     {
118     $this->db_mode = $mode;
119
120     // Already connected
121     if ($this->db_connected)
122       {
123       // no replication, current connection is ok
124       if ($this->db_dsnw==$this->db_dsnr)
125         return;
126
127       // connected to master, current connection is ok
128       if ($this->db_mode=='w')
129         return;
130
131       // Same mode, current connection is ok
132       if ($this->db_mode==$mode)
133         return;
134       }
135
136     if ($mode=='r')
137       $dsn = $this->db_dsnr;
138     else
139       $dsn = $this->db_dsnw;
140
141     $this->db_handle = $this->dsn_connect($dsn);
142     $this->db_connected = true;
143     }
144
981472 145
T 146   /**
147    * Activate/deactivate debug mode
148    *
149    * @param boolean True if SQL queries should be logged
150    */
151   function set_debug($dbg = true)
152   {
153     $this->debug_mode = $dbg;
154     if ($this->db_connected)
0ce1a6 155     {
981472 156       $this->db_handle->setOption('debug', $dbg);
0ce1a6 157       $this->db_handle->setOption('emulate_prepared', $dbg);
T 158     }
981472 159   }
d2a9db 160
T 161     
162   /**
163    * Getter for error state
164    *
165    * @param  boolean  True on error
166    */
167   function is_error()
168     {
169     return $this->db_error ? $this->db_error_msg : FALSE;
170     }
171     
172
173   /**
174    * Execute a SQL query
175    *
176    * @param  string  SQL query to execute
177    * @param  mixed   Values to be inserted in query
178    * @return number  Query handle identifier
179    * @access public
180    */
181   function query()
182     {
183     $params = func_get_args();
184     $query = array_shift($params);
185
186     return $this->_query($query, 0, 0, $params);
187     }
188
189
190   /**
191    * Execute a SQL query with limits
192    *
193    * @param  string  SQL query to execute
194    * @param  number  Offset for LIMIT statement
195    * @param  number  Number of rows for LIMIT statement
196    * @param  mixed   Values to be inserted in query
197    * @return number  Query handle identifier
198    * @access public
199    */
200   function limitquery()
201     {
202     $params = func_get_args();
203     $query = array_shift($params);
204     $offset = array_shift($params);
205     $numrows = array_shift($params);
206
207     return $this->_query($query, $offset, $numrows, $params);
208     }
209
210
211   /**
212    * Execute a SQL query with limits
213    *
214    * @param  string  SQL query to execute
215    * @param  number  Offset for LIMIT statement
216    * @param  number  Number of rows for LIMIT statement
217    * @param  array   Values to be inserted in query
218    * @return number  Query handle identifier
219    * @access private
220    */
221   function _query($query, $offset, $numrows, $params)
222     {
223     // Read or write ?
224     if (strtolower(trim(substr($query,0,6)))=='select')
225       $mode='r';
226     else
227       $mode='w';
228
229     $this->db_connect($mode);
230
231     if ($this->db_provider == 'sqlite')
232       $this->_sqlite_prepare();
233
234     if ($numrows || $offset)
235       $result = $this->db_handle->setLimit($numrows,$offset);
236
237     if (empty($params))
238         $result = $this->db_handle->query($query);
239     else
240       {
241       $params = (array)$params;
242       $q = $this->db_handle->prepare($query);
243       if ($this->db_handle->isError($q))
f45ec7 244         {
d2a9db 245         $this->db_error = TRUE;
T 246         $this->db_error_msg = $q->userinfo;
247
248         raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
249                           'message' => $this->db_error_msg), TRUE, TRUE);
f45ec7 250         }
d2a9db 251       else
f45ec7 252         {
d2a9db 253         $result = $q->execute($params);
T 254         $q->free();
f45ec7 255         }
d2a9db 256       }
T 257
258     // add result, even if it's an error
259     return $this->_add_result($result);
f45ec7 260     }
S 261
ccfda8 262
d2a9db 263   /**
T 264    * Get number of rows for a SQL query
265    * If no query handle is specified, the last query will be taken as reference
266    *
267    * @param  number  Optional query handle identifier
268    * @return mixed   Number of rows or FALSE on failure
269    * @access public
270    */
271   function num_rows($res_id=NULL)
10a699 272     {
d2a9db 273     if (!$this->db_handle)
T 274       return FALSE;
ccfda8 275
d2a9db 276     if ($result = $this->_get_result($res_id))
T 277       return $result->numRows();
278     else
279       return FALSE;
10a699 280     }
T 281
282
d2a9db 283   /**
T 284    * Get number of affected rows fort he last query
285    *
286    * @return mixed   Number of rows or FALSE on failure
287    * @access public
288    */
289   function affected_rows($result = null)
290     {
291     if (!$this->db_handle)
292       return FALSE;
ccfda8 293
bad419 294     return $this->_get_result($result);
d2a9db 295     }
ccfda8 296
d2a9db 297
T 298   /**
299    * Get last inserted record ID
300    * For Postgres databases, a sequence name is required
301    *
302    * @param  string  Sequence name for increment
303    * @return mixed   ID or FALSE on failure
304    * @access public
305    */
306   function insert_id($sequence = '')
307     {
308     if (!$this->db_handle || $this->db_mode=='r')
309       return FALSE;
310
311     return $this->db_handle->lastInsertID($sequence);
312     }
313
314
315   /**
316    * Get an associative array for one row
317    * If no query handle is specified, the last query will be taken as reference
318    *
319    * @param  number  Optional query handle identifier
320    * @return mixed   Array with col values or FALSE on failure
321    * @access public
322    */
323   function fetch_assoc($res_id=NULL)
324     {
325     $result = $this->_get_result($res_id);
326     return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC);
327     }
328
329
330   /**
331    * Get an index array for one row
332    * If no query handle is specified, the last query will be taken as reference
333    *
334    * @param  number  Optional query handle identifier
335    * @return mixed   Array with col values or FALSE on failure
336    * @access public
337    */
338   function fetch_array($res_id=NULL)
339     {
340     $result = $this->_get_result($res_id);
341     return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED);
342     }
343
344
345   /**
346    * Get co values for a result row
347    *
348    * @param  object  Query result handle
349    * @param  number  Fetch mode identifier
350    * @return mixed   Array with col values or FALSE on failure
351    * @access private
352    */
353   function _fetch_row($result, $mode)
354     {
355     if (PEAR::isError($result))
356       {
357       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
358                         'message' => $this->db_link->getMessage()), TRUE, FALSE);
359       return FALSE;
360       }
361
362     return $result->fetchRow($mode);
363     }
364
365
366   /**
367    * Formats input so it can be safely used in a query
368    *
369    * @param  mixed   Value to quote
370    * @return string  Quoted/converted string for use in query
371    * @access public
372    */
373   function quote($input, $type = null)
374     {
375     // create DB handle if not available
376     if (!$this->db_handle)
377       $this->db_connect('r');
378
379     // escape pear identifier chars
380     $rep_chars = array('?' => '\?',
381                        '!' => '\!',
382                        '&' => '\&');
383
384     return $this->db_handle->quote($input, $type);
385     }
386
387
388   /**
389    * Quotes a string so it can be safely used as a table or column name
390    *
391    * @param  string  Value to quote
392    * @return string  Quoted string for use in query
393    * @deprecated     Replaced by rcube_MDB2::quote_identifier
394    * @see            rcube_MDB2::quote_identifier
395    * @access public
396    */
397   function quoteIdentifier($str)
d7cb77 398     {
d2a9db 399     return $this->quote_identifier($str);
10a699 400     }
ccfda8 401
10a699 402
d2a9db 403   /**
T 404    * Quotes a string so it can be safely used as a table or column name
405    *
406    * @param  string  Value to quote
407    * @return string  Quoted string for use in query
408    * @access public
409    */
410   function quote_identifier($str)
f45ec7 411     {
d2a9db 412     if (!$this->db_handle)
T 413       $this->db_connect('r');
414
415     return $this->db_handle->quoteIdentifier($str);
f45ec7 416     }
S 417
8f4dcb 418   /**
T 419    * Escapes a string
420    *
421    * @param  string  The string to be escaped
422    * @return string  The escaped string
423    * @access public
424    * @since  0.1.1
425    */
426   function escapeSimple($str)
427     {
428     if (!$this->db_handle)
429       $this->db_connect('r');
430    
431     return $this->db_handle->escape($str);
432     }
433
f45ec7 434
d2a9db 435   /**
7139e3 436    * Return SQL function for current time and date
T 437    *
438    * @return string SQL function to use in query
439    * @access public
440    */
441   function now()
442     {
443     switch($this->db_provider)
444       {
445       case 'mssql':
446         return "getdate()";
447
448       default:
449         return "now()";
450       }
451     }
452
453
454   /**
d2a9db 455    * Return SQL statement to convert a field value into a unix timestamp
T 456    *
457    * @param  string  Field name
458    * @return string  SQL statement to use in query
459    * @access public
460    */
461   function unixtimestamp($field)
f45ec7 462     {
d2a9db 463     switch($this->db_provider)
T 464       {
465       case 'pgsql':
466         return "EXTRACT (EPOCH FROM $field)";
467         break;
468
7139e3 469       case 'mssql':
T 470         return "datediff(s, '1970-01-01 00:00:00', $field)";
471
d2a9db 472       default:
T 473         return "UNIX_TIMESTAMP($field)";
474       }
f45ec7 475     }
S 476
477
d2a9db 478   /**
T 479    * Return SQL statement to convert from a unix timestamp
480    *
481    * @param  string  Field name
482    * @return string  SQL statement to use in query
483    * @access public
484    */
485   function fromunixtime($timestamp)
f45ec7 486     {
d2a9db 487     switch($this->db_provider)
T 488       {
489       case 'mysqli':
490       case 'mysql':
491       case 'sqlite':
3978ab 492         return sprintf("FROM_UNIXTIME(%d)", $timestamp);
f45ec7 493
d2a9db 494       default:
T 495         return date("'Y-m-d H:i:s'", $timestamp);
496       }
f45ec7 497     }
S 498
d2a9db 499
T 500   /**
d8d416 501    * Return SQL statement for case insensitive LIKE
A 502    *
503    * @param  string  Field name
504    * @param  string  Search value
505    * @return string  SQL statement to use in query
506    * @access public
507    */
508   function ilike($column, $value)
509     {
510     // TODO: use MDB2's matchPattern() function
511     switch($this->db_provider)
512       {
513       case 'pgsql':
514         return $this->quote_identifier($column).' ILIKE '.$this->quote($value);
515       default:
516         return $this->quote_identifier($column).' LIKE '.$this->quote($value);
517       }
518     }
519
520
521   /**
d2a9db 522    * Adds a query result and returns a handle ID
T 523    *
524    * @param  object  Query handle
525    * @return mixed   Handle ID or FALE on failure
526    * @access private
527    */
528   function _add_result($res)
f45ec7 529     {
d2a9db 530     // sql error occured
T 531     if (PEAR::isError($res))
532       {
533       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
534                         'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
535       return FALSE;
536       }
537     else
538       {
539       $res_id = sizeof($this->a_query_results);
540       $this->a_query_results[$res_id] = $res;
541       $this->last_res_id = $res_id;
542       return $res_id;
543       }
f45ec7 544     }
d2a9db 545
T 546
547   /**
548    * Resolves a given handle ID and returns the according query handle
549    * If no ID is specified, the last ressource handle will be returned
550    *
551    * @param  number  Handle ID
552    * @return mixed   Ressource handle or FALE on failure
553    * @access private
554    */
555   function _get_result($res_id=NULL)
556     {
557     if ($res_id==NULL)
558       $res_id = $this->last_res_id;
559
560      if ($res_id && isset($this->a_query_results[$res_id]))
561        return $this->a_query_results[$res_id];
562      else
563        return FALSE;
564     }
565
566
567   /**
568    * Create a sqlite database from a file
569    *
570    * @param  object  SQLite database handle
571    * @param  string  File path to use for DB creation
572    * @access private
573    */
574   function _sqlite_create_database($dbh, $file_name)
575     {
576     if (empty($file_name) || !is_string($file_name))
577       return;
578
ff73e0 579     $data = file_get_contents($file_name);
d2a9db 580
T 581     if (strlen($data))
582       sqlite_exec($dbh->connection, $data);
583     }
584
585
586   /**
587    * Add some proprietary database functions to the current SQLite handle
588    * in order to make it MySQL compatible
589    *
590    * @access private
591    */
592   function _sqlite_prepare()
593     {
594     include_once('include/rcube_sqlite.inc');
595
596     // we emulate via callback some missing MySQL function
597     sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
598     sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
599     sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
600     sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");
601     }
602
603
604   }  // end class rcube_db
f45ec7 605
981472 606
T 607 /* this is our own debug handler for the MDB2 connection */
608 function mdb2_debug_handler(&$db, $scope, $message, $context = array())
609 {
610   if ($scope != 'prepare')
611   {
612     $debug_output = $scope . '('.$db->db_index.'): ';
613     $debug_output .= $message . $db->getOption('log_line_break');
614     write_log('sqllog', $debug_output);
615   }
616 }
617
618