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