source: subversion/trunk/roundcubemail/program/include/rcube_mdb2.php @ 1664

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