]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_db.inc
Merge commit 'debian/0.1_rc1_dfsg-1' into debian
[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-2007, 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 543 2007-04-28 18:07:12Z 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' => 603, '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         $result = &$this->db_handle->getOne("SELECT CURRVAL('$sequence')");
296         if (DB::isError($result))
297           raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 
298                             'message' => $result->getMessage()), TRUE, FALSE);
299         return $result;
300
301       case 'mssql':
302         $result = &$this->db_handle->getOne("SELECT @@IDENTITY");
303         if (DB::isError($result))
304           raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 
305                             'message' => $result->getMessage()), TRUE, FALSE);
306         return $result;
307                 
308       case 'mysql': // This is unfortuneate
309         return mysql_insert_id($this->db_handle->connection);
310
311       case 'mysqli':
312         return mysqli_insert_id($this->db_handle->connection);
313
314       case 'sqlite':
315         return sqlite_last_insert_rowid($this->db_handle->connection);
316
317       default:
318         die("portability issue with this database, please have the developer fix");
319       }
320     }
321
322
323   /**
324    * Get an associative array for one row
325    * If no query handle is specified, the last query will be taken as reference
326    *
327    * @param  number  Optional query handle identifier
328    * @return mixed   Array with col values or FALSE on failure
329    * @access public
330    */
331   function fetch_assoc($res_id=NULL)
332     {
333     $result = $this->_get_result($res_id);
334     return $this->_fetch_row($result, DB_FETCHMODE_ASSOC);
335     }
336
337
338   /**
339    * Get an index array for one row
340    * If no query handle is specified, the last query will be taken as reference
341    *
342    * @param  number  Optional query handle identifier
343    * @return mixed   Array with col values or FALSE on failure
344    * @access public
345    */
346   function fetch_array($res_id=NULL)
347     {
348     $result = $this->_get_result($res_id);
349     return $this->_fetch_row($result, DB_FETCHMODE_ORDERED);
350     }
351
352
353   /**
354    * Get co values for a result row
355    *
356    * @param  object  Query result handle
357    * @param  number  Fetch mode identifier
358    * @return mixed   Array with col values or FALSE on failure
359    * @access private
360    */
361   function _fetch_row($result, $mode)
362     {
363     if (!$result || DB::isError($result))
364       {
365       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
366                         'message' => $this->db_link->getMessage()), TRUE, FALSE);
367       return FALSE;
368       }
369       elseif (!is_object($result))
370       return FALSE;
371                          
372     return $result->fetchRow($mode);
373     }
374     
375
376   /**
377    * Formats input so it can be safely used in a query
378    *
379    * @param  mixed   Value to quote
380    * @return string  Quoted/converted string for use in query
381    * @access public
382    */
383   function quote($input)
384     {
385     // create DB handle if not available
386     if (!$this->db_handle)
387       $this->db_connect('r');
388       
389     // escape pear identifier chars
390     $rep_chars = array('?' => '\?',
391                        '!' => '\!',
392                        '&' => '\&');
393       
394     return $this->db_handle->quoteSmart(strtr($input, $rep_chars));
395     }
396     
397
398   /**
399    * Quotes a string so it can be safely used as a table or column name
400    *
401    * @param  string  Value to quote
402    * @return string  Quoted string for use in query
403    * @deprecated     Replaced by rcube_db::quote_identifier
404    * @see            rcube_db::quote_identifier
405    * @access public
406    */
407   function quoteIdentifier($str)
408         {
409     return $this->quote_identifier($str);
410         }
411
412
413   /**
414    * Quotes a string so it can be safely used as a table or column name
415    *
416    * @param  string  Value to quote
417    * @return string  Quoted string for use in query
418    * @access public
419    */
420   function quote_identifier($str)
421     {
422     if (!$this->db_handle)
423       $this->db_connect('r');
424                         
425     return $this->db_handle->quoteIdentifier($str);
426     }
427
428
429   /*
430    * Return SQL function for current time and date
431    *
432    * @return string SQL function to use in query
433    * @access public
434    */
435   function now()
436     {
437     switch($this->db_provider)
438       {
439       case 'mssql':
440         return "getdate()";
441
442       default:
443         return "now()";
444       }
445     }
446
447
448   /**
449    * Return SQL statement to convert a field value into a unix timestamp
450    *
451    * @param  string  Field name
452    * @return string  SQL statement to use in query
453    * @access public
454    */
455   function unixtimestamp($field)
456     {
457     switch($this->db_provider)
458       {
459       case 'pgsql':
460         return "EXTRACT (EPOCH FROM $field)";
461
462       case 'mssql':
463         return "datediff(s, '1970-01-01 00:00:00', $field)";
464
465       default:
466         return "UNIX_TIMESTAMP($field)";
467       }
468     }
469
470
471   /**
472    * Return SQL statement to convert from a unix timestamp
473    *
474    * @param  string  Field name
475    * @return string  SQL statement to use in query
476    * @access public
477    */
478   function fromunixtime($timestamp)
479     {
480     switch($this->db_provider)
481       {
482       case 'mysqli':
483       case 'mysql':
484       case 'sqlite':
485         return sprintf("FROM_UNIXTIME(%d)", $timestamp);
486
487       default:
488         return date("'Y-m-d H:i:s'", $timestamp);
489       }
490     }
491
492
493   /**
494    * Adds a query result and returns a handle ID
495    *
496    * @param  object  Query handle
497    * @return mixed   Handle ID or FALE on failure
498    * @access private
499    */
500   function _add_result($res)
501     {
502     // sql error occured
503     if (DB::isError($res))
504       {
505       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
506                         'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
507       return FALSE;
508       }
509     else
510       {
511       $res_id = sizeof($this->a_query_results);
512       $this->a_query_results[$res_id] = $res;
513       $this->last_res_id = $res_id;
514       return $res_id;
515       }
516     }
517
518
519   /**
520    * Resolves a given handle ID and returns the according query handle
521    * If no ID is specified, the last ressource handle will be returned
522    *
523    * @param  number  Handle ID
524    * @return mixed   Ressource handle or FALE on failure
525    * @access private
526    */
527   function _get_result($res_id=NULL)
528     {
529     if ($res_id==NULL)
530       $res_id = $this->last_res_id;
531     
532      if ($res_id && isset($this->a_query_results[$res_id]))
533        return $this->a_query_results[$res_id];
534      else
535        return FALSE;
536     }
537
538
539   /**
540    * Create a sqlite database from a file
541    *
542    * @param  object  SQLite database handle
543    * @param  string  File path to use for DB creation
544    * @access private
545    */
546   function _sqlite_create_database($dbh, $file_name)
547     {
548     if (empty($file_name) || !is_string($file_name))
549       return;
550
551     $data = '';
552     if ($fd = fopen($file_name, 'r'))
553       {
554       $data = fread($fd, filesize($file_name));
555       fclose($fd);
556       }
557
558     if (strlen($data))
559       sqlite_exec($dbh->connection, $data);
560     }
561
562
563   /**
564    * Add some proprietary database functions to the current SQLite handle
565    * in order to make it MySQL compatible
566    *
567    * @access private
568    */
569   function _sqlite_prepare()
570     {
571     include_once('include/rcube_sqlite.inc');
572
573     // we emulate via callback some missing MySQL function
574     sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
575     sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
576     sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
577     sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");    
578     }
579
580
581   }  // end class rcube_db
582
583 ?>