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