thomascube
2006-11-22 d04d202234b0ba1e65b1c581acf0cbe715120dd7
commit | author | age
f45ec7 1 <?php
S 2
3 /*
4  +-----------------------------------------------------------------------+
d2a9db 5  | program/include/rcube_mdb2.inc                                        |
f45ec7 6  |                                                                       |
S 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:                                                              |
d2a9db 12  |   PEAR:DB wrapper class that implements PEAR MDB2 functions           |
T 13  |   See http://pear.php.net/package/MDB2                                |
f45ec7 14  |                                                                       |
S 15  +-----------------------------------------------------------------------+
d2a9db 16  | Author: Lukas Kahwe Smith <smith@pooteeweet.org>                      |
f45ec7 17  +-----------------------------------------------------------------------+
S 18
19  $Id$
20
21 */
22
d2a9db 23
T 24 /**
25  * Obtain the PEAR::DB class that is used for abstraction
26  */
f45ec7 27 require_once('MDB2.php');
S 28
d2a9db 29
T 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  */
f45ec7 42 class rcube_db
d2a9db 43   {
T 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 = '';
f45ec7 51
d2a9db 52   var $a_query_results = array('dummy');
T 53   var $last_res_id = 0;
f45ec7 54
d2a9db 55
T 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='')
f45ec7 63     {
d2a9db 64     if ($db_dsnr=='')
T 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'];
f45ec7 72     }
S 73
d2a9db 74
T 75   /**
76    * PHP 4 object constructor
77    *
78    * @see  rcube_MDB2::__construct
79    */
80   function rcube_db($db_dsnw,$db_dsnr='')
f45ec7 81     {
d2a9db 82     $this->__construct($db_dsnw,$db_dsnr);
f45ec7 83     }
S 84
d2a9db 85
T 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)
f45ec7 94     {
d2a9db 95     // Use persistent connections if available
T 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__,
f45ec7 104                         'message' => $dbh->getMessage()), TRUE, FALSE);
d2a9db 105       }
f45ec7 106
d2a9db 107     else if ($this->db_provider=='sqlite')
T 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))
f45ec7 238         {
d2a9db 239         $this->db_error = TRUE;
T 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);
f45ec7 244         }
d2a9db 245       else
f45ec7 246         {
d2a9db 247         $result = $q->execute($params);
T 248         $q->free();
f45ec7 249         }
d2a9db 250       }
T 251
252     // add result, even if it's an error
253     return $this->_add_result($result);
f45ec7 254     }
S 255
ccfda8 256
d2a9db 257   /**
T 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)
10a699 266     {
d2a9db 267     if (!$this->db_handle)
T 268       return FALSE;
ccfda8 269
d2a9db 270     if ($result = $this->_get_result($res_id))
T 271       return $result->numRows();
272     else
273       return FALSE;
10a699 274     }
T 275
276
d2a9db 277   /**
T 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;
ccfda8 287
d2a9db 288     return $result;
T 289     }
ccfda8 290
d2a9db 291
T 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)
d7cb77 392     {
d2a9db 393     return $this->quote_identifier($str);
10a699 394     }
ccfda8 395
10a699 396
d2a9db 397   /**
T 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)
f45ec7 405     {
d2a9db 406     if (!$this->db_handle)
T 407       $this->db_connect('r');
408
409     return $this->db_handle->quoteIdentifier($str);
f45ec7 410     }
S 411
412
d2a9db 413   /**
7139e3 414    * Return SQL function for current time and date
T 415    *
416    * @return string SQL function to use in query
417    * @access public
418    */
419   function now()
420     {
421     switch($this->db_provider)
422       {
423       case 'mssql':
424         return "getdate()";
425
426       default:
427         return "now()";
428       }
429     }
430
431
432   /**
d2a9db 433    * Return SQL statement to convert a field value into a unix timestamp
T 434    *
435    * @param  string  Field name
436    * @return string  SQL statement to use in query
437    * @access public
438    */
439   function unixtimestamp($field)
f45ec7 440     {
d2a9db 441     switch($this->db_provider)
T 442       {
443       case 'pgsql':
444         return "EXTRACT (EPOCH FROM $field)";
445         break;
446
7139e3 447       case 'mssql':
T 448         return "datediff(s, '1970-01-01 00:00:00', $field)";
449
d2a9db 450       default:
T 451         return "UNIX_TIMESTAMP($field)";
452       }
f45ec7 453     }
S 454
455
d2a9db 456   /**
T 457    * Return SQL statement to convert from a unix timestamp
458    *
459    * @param  string  Field name
460    * @return string  SQL statement to use in query
461    * @access public
462    */
463   function fromunixtime($timestamp)
f45ec7 464     {
d2a9db 465     switch($this->db_provider)
T 466       {
467       case 'mysqli':
468       case 'mysql':
469       case 'sqlite':
470         return "FROM_UNIXTIME($timestamp)";
f45ec7 471
d2a9db 472       default:
T 473         return date("'Y-m-d H:i:s'", $timestamp);
474       }
f45ec7 475     }
S 476
d2a9db 477
T 478   /**
479    * Adds a query result and returns a handle ID
480    *
481    * @param  object  Query handle
482    * @return mixed   Handle ID or FALE on failure
483    * @access private
484    */
485   function _add_result($res)
f45ec7 486     {
d2a9db 487     // sql error occured
T 488     if (PEAR::isError($res))
489       {
490       raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
491                         'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
492       return FALSE;
493       }
494     else
495       {
496       $res_id = sizeof($this->a_query_results);
497       $this->a_query_results[$res_id] = $res;
498       $this->last_res_id = $res_id;
499       return $res_id;
500       }
f45ec7 501     }
d2a9db 502
T 503
504   /**
505    * Resolves a given handle ID and returns the according query handle
506    * If no ID is specified, the last ressource handle will be returned
507    *
508    * @param  number  Handle ID
509    * @return mixed   Ressource handle or FALE on failure
510    * @access private
511    */
512   function _get_result($res_id=NULL)
513     {
514     if ($res_id==NULL)
515       $res_id = $this->last_res_id;
516
517      if ($res_id && isset($this->a_query_results[$res_id]))
518        return $this->a_query_results[$res_id];
519      else
520        return FALSE;
521     }
522
523
524   /**
525    * Create a sqlite database from a file
526    *
527    * @param  object  SQLite database handle
528    * @param  string  File path to use for DB creation
529    * @access private
530    */
531   function _sqlite_create_database($dbh, $file_name)
532     {
533     if (empty($file_name) || !is_string($file_name))
534       return;
535
536     $data = '';
537     if ($fd = fopen($file_name, 'r'))
538       {
539       $data = fread($fd, filesize($file_name));
540       fclose($fd);
541       }
542
543     if (strlen($data))
544       sqlite_exec($dbh->connection, $data);
545     }
546
547
548   /**
549    * Add some proprietary database functions to the current SQLite handle
550    * in order to make it MySQL compatible
551    *
552    * @access private
553    */
554   function _sqlite_prepare()
555     {
556     include_once('include/rcube_sqlite.inc');
557
558     // we emulate via callback some missing MySQL function
559     sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
560     sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
561     sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
562     sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");
563     }
564
565
566   }  // end class rcube_db
f45ec7 567
S 568 ?>