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