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