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