]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_db.inc
Imported Upstream version 0.1~beta2.2~dfsg
[roundcube.git] / program / include / rcube_db.inc
1 <?php
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  +-----------------------------------------------------------------------+
16  | Author: David Saez Padros <david@ols.es>                              |
17  |         Thomas Bruederli <roundcube@gmail.com>                        |
18  +-----------------------------------------------------------------------+
19
20  $Id: rcube_db.inc 262 2006-06-25 09:17:07Z thomasb $
21
22 */
23
24
25 /**
26  * Obtain the PEAR::DB class that is used for abstraction
27  */
28 require_once('DB.php');
29
30
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>
39  * @version    1.17
40  * @link       http://pear.php.net/package/DB
41  */
42 class rcube_db
43   {
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
49   var $db_pconn = false;      // Use persistent connections
50   var $db_error = false;
51   var $db_error_msg = '';
52
53   var $a_query_results = array('dummy');
54   var $last_res_id = 0;
55
56
57   /**
58    * Object constructor
59    *
60    * @param  string  DSN for read/write operations
61    * @param  string  Optional DSN for read only operations
62    */
63   function __construct($db_dsnw, $db_dsnr='', $pconn=false)
64     {
65     if ($db_dsnr=='')
66       $db_dsnr=$db_dsnw;
67         
68     $this->db_dsnw = $db_dsnw;
69     $this->db_dsnr = $db_dsnr;
70     $this->db_pconn = $pconn;
71         
72     $dsn_array = DB::parseDSN($db_dsnw);
73     $this->db_provider = $dsn_array['phptype'];        
74     }
75
76
77   /**
78    * PHP 4 object constructor
79    *
80    * @see  rcube_db::__construct
81    */
82   function rcube_db($db_dsnw, $db_dsnr='', $pconn=false)
83     {
84     $this->__construct($db_dsnw, $db_dsnr, $pconn);
85     }
86
87
88   /**
89    * Connect to specific database
90    *
91    * @param  string  DSN for DB connections
92    * @return object  PEAR database handle
93    * @access private
94    */
95   function dsn_connect($dsn)
96     {
97     // Use persistent connections if available
98     $dbh = DB::connect($dsn, array('persistent' => $this->db_pconn));
99         
100     if (DB::isError($dbh))
101       {
102       $this->db_error = TRUE;
103       $this->db_error_msg = $dbh->getMessage();
104
105       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
106                         'message' => $this->db_error_msg), TRUE, FALSE);
107                         
108       return FALSE;
109       }
110
111     else if ($this->db_provider=='sqlite')
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       }
117         
118     return $dbh;
119     }
120
121
122   /**
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)
130     {
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;
139             
140       // connected to master, current connection is ok
141       if ($this->db_mode=='w')
142         return;
143
144       // Same mode, current connection is ok
145       if ($this->db_mode==$mode)
146         return;
147       }
148      
149     if ($mode=='r')
150       $dsn = $this->db_dsnr;
151     else
152       $dsn = $this->db_dsnw;
153
154     $this->db_handle = $this->dsn_connect($dsn);
155     $this->db_connected = $this->db_handle ? TRUE : FALSE;
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;
167     }
168
169
170   /**
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    */
178   function query()
179     {
180     $params = func_get_args();
181     $query = array_shift($params);
182
183     return $this->_query($query, 0, 0, $params);
184     }
185
186
187   /**
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    */
197   function limitquery()
198     {
199     $params = func_get_args();
200     $query = array_shift($params);
201     $offset = array_shift($params);
202     $numrows = array_shift($params);
203                 
204     return $this->_query($query, $offset, $numrows, $params);
205     }
206
207
208   /**
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    */
218   function _query($query, $offset, $numrows, $params)
219     {
220     // Read or write ?
221     if (strtolower(trim(substr($query,0,6)))=='select')
222       $mode='r';
223     else
224       $mode='w';
225         
226     $this->db_connect($mode);
227     
228     if (!$this->db_connected)
229       return FALSE;
230
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);
241     }
242
243
244   /**
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    */
252   function num_rows($res_id=NULL)
253     {
254     if (!$this->db_handle)
255       return FALSE;
256
257     if ($result = $this->_get_result($res_id))    
258       return $result->numRows();
259     else
260       return FALSE;
261     }
262
263
264   /**
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()
271     {
272     if (!$this->db_handle)
273       return FALSE;
274
275     return $this->db_handle->affectedRows();
276     }
277
278
279   /**
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    */
287   function insert_id($sequence = '')
288     {
289     if (!$this->db_handle || $this->db_mode=='r')
290       return FALSE;
291
292     switch($this->db_provider)
293       {
294       case 'pgsql':
295         // PostgreSQL uses sequences
296         $result = &$this->db_handle->getOne("SELECT CURRVAL('$sequence')");
297         if (DB::isError($result))
298           {
299           raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 
300                             'message' => $result->getMessage()), TRUE, FALSE);
301           }
302
303         return $result;
304                 
305       case 'mysql': // This is unfortuneate
306         return mysql_insert_id($this->db_handle->connection);
307
308       case 'mysqli':
309         return mysqli_insert_id($this->db_handle->connection);
310         
311       case 'sqlite':
312         return sqlite_last_insert_rowid($this->db_handle->connection);
313
314       default:
315         die("portability issue with this database, please have the developer fix");
316       }
317     }
318
319
320   /**
321    * Get an associative array for one row
322    * If no query handle is specified, the last query will be taken as reference
323    *
324    * @param  number  Optional query handle identifier
325    * @return mixed   Array with col values or FALSE on failure
326    * @access public
327    */
328   function fetch_assoc($res_id=NULL)
329     {
330     $result = $this->_get_result($res_id);
331     return $this->_fetch_row($result, DB_FETCHMODE_ASSOC);
332     }
333
334
335   /**
336    * Get an index array for one row
337    * If no query handle is specified, the last query will be taken as reference
338    *
339    * @param  number  Optional query handle identifier
340    * @return mixed   Array with col values or FALSE on failure
341    * @access public
342    */
343   function fetch_array($res_id=NULL)
344     {
345     $result = $this->_get_result($res_id);
346     return $this->_fetch_row($result, DB_FETCHMODE_ORDERED);
347     }
348
349
350   /**
351    * Get co values for a result row
352    *
353    * @param  object  Query result handle
354    * @param  number  Fetch mode identifier
355    * @return mixed   Array with col values or FALSE on failure
356    * @access private
357    */
358   function _fetch_row($result, $mode)
359     {
360     if (DB::isError($result))
361       {
362       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
363                         'message' => $this->db_link->getMessage()), TRUE, FALSE);
364       return FALSE;
365       }
366                          
367     return $result->fetchRow($mode);
368     }
369     
370
371   /**
372    * Formats input so it can be safely used in a query
373    *
374    * @param  mixed   Value to quote
375    * @return string  Quoted/converted string for use in query
376    * @access public
377    */
378   function quote($input)
379     {
380     // create DB handle if not available
381     if (!$this->db_handle)
382       $this->db_connect('r');
383       
384     // escape pear identifier chars
385     $rep_chars = array('?' => '\?',
386                        '!' => '\!',
387                        '&' => '\&');
388       
389     return $this->db_handle->quoteSmart(strtr($input, $rep_chars));
390     }
391     
392
393   /**
394    * Quotes a string so it can be safely used as a table or column name
395    *
396    * @param  string  Value to quote
397    * @return string  Quoted string for use in query
398    * @deprecated     Replaced by rcube_db::quote_identifier
399    * @see            rcube_db::quote_identifier
400    * @access public
401    */
402   function quoteIdentifier($str)
403         {
404     return $this->quote_identifier($str);
405         }
406
407
408   /**
409    * Quotes a string so it can be safely used as a table or column name
410    *
411    * @param  string  Value to quote
412    * @return string  Quoted string for use in query
413    * @access public
414    */
415   function quote_identifier($str)
416     {
417     if (!$this->db_handle)
418       $this->db_connect('r');
419                         
420     return $this->db_handle->quoteIdentifier($str);
421     }
422
423
424   /**
425    * Return SQL statement to convert a field value into a unix timestamp
426    *
427    * @param  string  Field name
428    * @return string  SQL statement to use in query
429    * @access public
430    */
431   function unixtimestamp($field)
432     {
433     switch($this->db_provider)
434       {
435       case 'pgsql':
436         return "EXTRACT (EPOCH FROM $field)";
437         break;
438
439       default:
440         return "UNIX_TIMESTAMP($field)";
441       }
442     }
443
444
445   /**
446    * Return SQL statement to convert from a unix timestamp
447    *
448    * @param  string  Field name
449    * @return string  SQL statement to use in query
450    * @access public
451    */
452   function fromunixtime($timestamp)
453     {
454     switch($this->db_provider)
455       {
456       case 'mysqli':
457       case 'mysql':
458       case 'sqlite':
459         return "FROM_UNIXTIME($timestamp)";
460
461       default:
462         return date("'Y-m-d H:i:s'", $timestamp);
463       }
464     }
465
466
467   /**
468    * Adds a query result and returns a handle ID
469    *
470    * @param  object  Query handle
471    * @return mixed   Handle ID or FALE on failure
472    * @access private
473    */
474   function _add_result($res)
475     {
476     // sql error occured
477     if (DB::isError($res))
478       {
479       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
480                         'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
481       return FALSE;
482       }
483     else
484       {
485       $res_id = sizeof($this->a_query_results);
486       $this->a_query_results[$res_id] = $res;
487       $this->last_res_id = $res_id;
488       return $res_id;
489       }
490     }
491
492
493   /**
494    * Resolves a given handle ID and returns the according query handle
495    * If no ID is specified, the last ressource handle will be returned
496    *
497    * @param  number  Handle ID
498    * @return mixed   Ressource handle or FALE on failure
499    * @access private
500    */
501   function _get_result($res_id=NULL)
502     {
503     if ($res_id==NULL)
504       $res_id = $this->last_res_id;
505     
506      if ($res_id && isset($this->a_query_results[$res_id]))
507        return $this->a_query_results[$res_id];
508      else
509        return FALSE;
510     }
511
512
513   /**
514    * Create a sqlite database from a file
515    *
516    * @param  object  SQLite database handle
517    * @param  string  File path to use for DB creation
518    * @access private
519    */
520   function _sqlite_create_database($dbh, $file_name)
521     {
522     if (empty($file_name) || !is_string($file_name))
523       return;
524
525     $data = '';
526     if ($fd = fopen($file_name, 'r'))
527       {
528       $data = fread($fd, filesize($file_name));
529       fclose($fd);
530       }
531
532     if (strlen($data))
533       sqlite_exec($dbh->connection, $data);
534     }
535
536
537   /**
538    * Add some proprietary database functions to the current SQLite handle
539    * in order to make it MySQL compatible
540    *
541    * @access private
542    */
543   function _sqlite_prepare()
544     {
545     include_once('include/rcube_sqlite.inc');
546
547     // we emulate via callback some missing MySQL function
548     sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
549     sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
550     sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
551     sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");    
552     }
553
554
555   }  // end class rcube_db
556
557 ?>