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

Last change on this file since 137 was 137, checked in by roundcube, 7 years ago

Added fetch_array method

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