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