source: subversion/trunk/roundcubemail/program/include/rcube_db.inc @ 504

Last change on this file since 504 was 504, checked in by thomasb, 6 years ago

Fixed bugs in rcube_db and rcube_imap classes

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 14.8 KB
RevLine 
[12]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, 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 +-----------------------------------------------------------------------+
[18]16 | Author: David Saez Padros <david@ols.es>                              |
[262]17 |         Thomas Bruederli <roundcube@gmail.com>                        |
[12]18 +-----------------------------------------------------------------------+
19
20 $Id$
21
22*/
23
[93]24
25/**
26 * Obtain the PEAR::DB class that is used for abstraction
27 */
[12]28require_once('DB.php');
29
[93]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>
[164]39 * @version    1.17
[93]40 * @link       http://pear.php.net/package/DB
41 */
[12]42class rcube_db
[88]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
[164]49  var $db_pconn = false;      // Use persistent connections
50  var $db_error = false;
51  var $db_error_msg = '';
[12]52
[88]53  var $a_query_results = array('dummy');
54  var $last_res_id = 0;
[12]55
[88]56
[93]57  /**
58   * Object constructor
59   *
60   * @param  string  DSN for read/write operations
61   * @param  string  Optional DSN for read only operations
62   */
[164]63  function __construct($db_dsnw, $db_dsnr='', $pconn=false)
[12]64    {
[88]65    if ($db_dsnr=='')
66      $db_dsnr=$db_dsnw;
[18]67       
[88]68    $this->db_dsnw = $db_dsnw;
69    $this->db_dsnr = $db_dsnr;
[164]70    $this->db_pconn = $pconn;
[21]71       
[88]72    $dsn_array = DB::parseDSN($db_dsnw);
73    $this->db_provider = $dsn_array['phptype'];       
[12]74    }
75
[88]76
[93]77  /**
78   * PHP 4 object constructor
79   *
80   * @see  rcube_db::__construct
81   */
[164]82  function rcube_db($db_dsnw, $db_dsnr='', $pconn=false)
[12]83    {
[262]84    $this->__construct($db_dsnw, $db_dsnr, $pconn);
[12]85    }
86
[88]87
[93]88  /**
89   * Connect to specific database
90   *
91   * @param  string  DSN for DB connections
92   * @return object  PEAR database handle
93   * @access private
94   */
[88]95  function dsn_connect($dsn)
[18]96    {
[88]97    // Use persistent connections if available
[164]98    $dbh = DB::connect($dsn, array('persistent' => $this->db_pconn));
[21]99       
[88]100    if (DB::isError($dbh))
[93]101      {
[164]102      $this->db_error = TRUE;
103      $this->db_error_msg = $dbh->getMessage();
104
[93]105      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
[164]106                        'message' => $this->db_error_msg), TRUE, FALSE);
107                       
108      return FALSE;
[93]109      }
[21]110
[88]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      }
[20]117       
[88]118    return $dbh;
[18]119    }
[12]120
[88]121
[93]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)
[18]130    {
[88]131    $this->db_mode = $mode;
[21]132
[88]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;
[18]139           
[88]140      // connected to master, current connection is ok
141      if ($this->db_mode=='w')
142        return;
[12]143
[88]144      // Same mode, current connection is ok
145      if ($this->db_mode==$mode)
146        return;
147      }
[93]148     
[88]149    if ($mode=='r')
150      $dsn = $this->db_dsnr;
151    else
152      $dsn = $this->db_dsnw;
[12]153
[88]154    $this->db_handle = $this->dsn_connect($dsn);
[164]155    $this->db_connected = $this->db_handle ? TRUE : FALSE;
[18]156    }
[164]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    }
[12]168
[88]169
[93]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   */
[88]178  function query()
[12]179    {
[88]180    $params = func_get_args();
181    $query = array_shift($params);
182
183    return $this->_query($query, 0, 0, $params);
[58]184    }
[88]185
186
[93]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   */
[88]197  function limitquery()
[58]198    {
[88]199    $params = func_get_args();
200    $query = array_shift($params);
201    $offset = array_shift($params);
202    $numrows = array_shift($params);
[58]203               
[88]204    return $this->_query($query, $offset, $numrows, $params);
[58]205    }
[88]206
207
[93]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   */
[88]218  function _query($query, $offset, $numrows, $params)
[58]219    {
[88]220    // Read or write ?
221    if (strtolower(trim(substr($query,0,6)))=='select')
222      $mode='r';
223    else
224      $mode='w';
[18]225       
[88]226    $this->db_connect($mode);
[164]227   
228    if (!$this->db_connected)
229      return FALSE;
[20]230
[88]231    if ($this->db_provider == 'sqlite')
232      $this->_sqlite_prepare();
[58]233
[88]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    }
[58]242
[88]243
[93]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   */
[88]252  function num_rows($res_id=NULL)
[12]253    {
[88]254    if (!$this->db_handle)
255      return FALSE;
[12]256
[88]257    if ($result = $this->_get_result($res_id))   
258      return $result->numRows();
259    else
260      return FALSE;
[12]261    }
262
[88]263
[93]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()
[12]271    {
[88]272    if (!$this->db_handle)
273      return FALSE;
274
275    return $this->db_handle->affectedRows();
[12]276    }
277
[88]278
[93]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   */
[88]287  function insert_id($sequence = '')
[12]288    {
[88]289    if (!$this->db_handle || $this->db_mode=='r')
290      return FALSE;
[12]291
[88]292    switch($this->db_provider)
293      {
294      case 'pgsql':
[93]295        $result = &$this->db_handle->getOne("SELECT CURRVAL('$sequence')");
[504]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
[328]301      case 'mssql':
[504]302        $result = &$this->db_handle->getOne("SELECT @@IDENTITY");
[88]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;
[12]307               
[88]308      case 'mysql': // This is unfortuneate
309        return mysql_insert_id($this->db_handle->connection);
[20]310
[88]311      case 'mysqli':
312        return mysqli_insert_id($this->db_handle->connection);
[504]313
[88]314      case 'sqlite':
315        return sqlite_last_insert_rowid($this->db_handle->connection);
[20]316
[88]317      default:
318        die("portability issue with this database, please have the developer fix");
319      }
[12]320    }
321
322
[93]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   */
[88]331  function fetch_assoc($res_id=NULL)
[12]332    {
[88]333    $result = $this->_get_result($res_id);
[137]334    return $this->_fetch_row($result, DB_FETCHMODE_ASSOC);
335    }
[12]336
[137]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    {
[504]363    if (!$result || DB::isError($result))
[88]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      }
[12]369                         
[137]370    return $result->fetchRow($mode);
[12]371    }
[137]372   
[12]373
[93]374  /**
375   * Formats input so it can be safely used in a query
376   *
377   * @param  mixed   Value to quote
378   * @return string  Quoted/converted string for use in query
379   * @access public
380   */
381  function quote($input)
[76]382    {
[93]383    // create DB handle if not available
[88]384    if (!$this->db_handle)
385      $this->db_connect('r');
[93]386     
387    // escape pear identifier chars
388    $rep_chars = array('?' => '\?',
389                       '!' => '\!',
390                       '&' => '\&');
391     
392    return $this->db_handle->quoteSmart(strtr($input, $rep_chars));
[76]393    }
394   
395
[93]396  /**
397   * Quotes a string so it can be safely used as a table or column name
398   *
399   * @param  string  Value to quote
400   * @return string  Quoted string for use in query
401   * @deprecated     Replaced by rcube_db::quote_identifier
402   * @see            rcube_db::quote_identifier
403   * @access public
404   */
[88]405  function quoteIdentifier($str)
[58]406        {
[93]407    return $this->quote_identifier($str);
[58]408        }
[76]409
[88]410
[93]411  /**
412   * Quotes a string so it can be safely used as a table or column name
413   *
414   * @param  string  Value to quote
415   * @return string  Quoted string for use in query
416   * @access public
417   */
[88]418  function quote_identifier($str)
[12]419    {
[93]420    if (!$this->db_handle)
421      $this->db_connect('r');
422                       
423    return $this->db_handle->quoteIdentifier($str);
[12]424    }
425
426
[328]427  /*
428   * Return SQL function for current time and date
429   *
430   * @return string SQL function to use in query
431   * @access public
432   */
433  function now()
434    {
435    switch($this->db_provider)
436      {
437      case 'mssql':
438        return "getdate()";
439
440      default:
441        return "now()";
442      }
443    }
444
445
[93]446  /**
447   * Return SQL statement to convert a field value into a unix timestamp
448   *
449   * @param  string  Field name
450   * @return string  SQL statement to use in query
451   * @access public
452   */
[88]453  function unixtimestamp($field)
[12]454    {
[88]455    switch($this->db_provider)
456      {
457      case 'pgsql':
458        return "EXTRACT (EPOCH FROM $field)";
459
[328]460      case 'mssql':
461        return "datediff(s, '1970-01-01 00:00:00', $field)";
462
[88]463      default:
464        return "UNIX_TIMESTAMP($field)";
465      }
466    }
467
468
[93]469  /**
470   * Return SQL statement to convert from a unix timestamp
471   *
472   * @param  string  Field name
473   * @return string  SQL statement to use in query
474   * @access public
475   */
[88]476  function fromunixtime($timestamp)
477    {
478    switch($this->db_provider)
479      {
480      case 'mysqli':
481      case 'mysql':
482      case 'sqlite':
[328]483        return sprintf("FROM_UNIXTIME(%d)", $timestamp);
[88]484
485      default:
486        return date("'Y-m-d H:i:s'", $timestamp);
487      }
488    }
489
490
[93]491  /**
492   * Adds a query result and returns a handle ID
493   *
494   * @param  object  Query handle
495   * @return mixed   Handle ID or FALE on failure
496   * @access private
497   */
[88]498  function _add_result($res)
499    {
500    // sql error occured
501    if (DB::isError($res))
502      {
503      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
[93]504                        'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
[88]505      return FALSE;
506      }
507    else
508      {
509      $res_id = sizeof($this->a_query_results);
510      $this->a_query_results[$res_id] = $res;
511      $this->last_res_id = $res_id;
512      return $res_id;
513      }
514    }
515
516
[93]517  /**
518   * Resolves a given handle ID and returns the according query handle
519   * If no ID is specified, the last ressource handle will be returned
520   *
521   * @param  number  Handle ID
522   * @return mixed   Ressource handle or FALE on failure
523   * @access private
524   */
525  function _get_result($res_id=NULL)
[88]526    {
527    if ($res_id==NULL)
528      $res_id = $this->last_res_id;
[12]529   
[88]530     if ($res_id && isset($this->a_query_results[$res_id]))
531       return $this->a_query_results[$res_id];
532     else
533       return FALSE;
[12]534    }
535
[20]536
[93]537  /**
538   * Create a sqlite database from a file
539   *
540   * @param  object  SQLite database handle
541   * @param  string  File path to use for DB creation
542   * @access private
543   */
544  function _sqlite_create_database($dbh, $file_name)
[20]545    {
[93]546    if (empty($file_name) || !is_string($file_name))
547      return;
[20]548
[88]549    $data = '';
[93]550    if ($fd = fopen($file_name, 'r'))
[88]551      {
[93]552      $data = fread($fd, filesize($file_name));
[88]553      fclose($fd);
554      }
[20]555
[88]556    if (strlen($data))
557      sqlite_exec($dbh->connection, $data);
[20]558    }
559
[93]560
561  /**
562   * Add some proprietary database functions to the current SQLite handle
563   * in order to make it MySQL compatible
564   *
565   * @access private
566   */
[88]567  function _sqlite_prepare()
[20]568    {
[88]569    include_once('include/rcube_sqlite.inc');
[20]570
[88]571    // we emulate via callback some missing MySQL function
572    sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
573    sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
574    sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
575    sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");   
[20]576    }
[12]577
[88]578
579  }  // end class rcube_db
580
[12]581?>
Note: See TracBrowser for help on using the repository browser.