blob: ef37967dcb13a58c2c263fa3955635882615c877 [file] [log] [blame]
admin7b613c72006-09-24 18:05:17 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * Code Igniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
8 * @author Rick Ellis
9 * @copyright Copyright (c) 2006, pMachine, Inc.
10 * @license http://www.codeignitor.com/user_guide/license.html
11 * @link http://www.codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Database Driver Class
20 *
21 * This is the platform-independent base DB implementation class.
22 * This class will not be called directly. Rather, the adapter
23 * class for the specific database will extend and instantiate it.
24 *
25 * @package CodeIgniter
26 * @subpackage Drivers
27 * @category Database
28 * @author Rick Ellis
29 * @link http://www.codeigniter.com/user_guide/database/
30 */
31class CI_DB_driver {
32
33 var $username;
34 var $password;
35 var $hostname;
36 var $database;
37 var $dbdriver = 'mysql';
38 var $dbprefix = '';
39 var $port = '';
40 var $pconnect = FALSE;
41 var $conn_id = FALSE;
42 var $result_id = FALSE;
43 var $db_debug = FALSE;
44 var $benchmark = 0;
45 var $query_count = 0;
46 var $bind_marker = '?';
47 var $queries = array();
48 var $trans_enabled = TRUE;
49 var $_trans_depth = 0;
50 var $_trans_failure = FALSE; // Used with transactions to determine if a rollback should occur
51
52 // These are use with Oracle
53 var $stmt_id;
54 var $curs_id;
55 var $limit_used;
56
57
58 /**
59 * Constructor. Accepts one parameter containing the database
60 * connection settings.
61 *
62 * Database settings can be passed as discreet
63 * parameters or as a data source name in the first
64 * parameter. DSNs must have this prototype:
65 * $dsn = 'driver://username:password@hostname/database';
66 *
67 * @param mixed. Can be an array or a DSN string
68 */
69 function CI_DB_driver($params)
70 {
71 $this->initialize($params);
72 log_message('debug', 'Database Driver Class Initialized');
73 }
74
75 // --------------------------------------------------------------------
76
77 /**
78 * Initialize Database Settings
79 *
80 * @access private Called by the constructor
81 * @param mixed
82 * @return void
83 */
84 function initialize($params = '')
85 {
86 if (is_array($params))
87 {
88 foreach (array('hostname' => '', 'username' => '', 'password' => '', 'database' => '', 'dbdriver' => 'mysql', 'dbprefix' => '', 'port' => '', 'pconnect' => FALSE, 'db_debug' => FALSE) as $key => $val)
89 {
90 $this->$key = ( ! isset($params[$key])) ? $val : $params[$key];
91 }
92 }
93 elseif (strpos($params, '://'))
94 {
95 if (FALSE === ($dsn = @parse_url($params)))
96 {
97 log_message('error', 'Invalid DB Connection String');
98
99 if ($this->db_debug)
100 {
101 return $this->display_error('db_invalid_connection_str');
102 }
103 return FALSE;
104 }
105
106 $this->hostname = ( ! isset($dsn['host'])) ? '' : rawurldecode($dsn['host']);
107 $this->username = ( ! isset($dsn['user'])) ? '' : rawurldecode($dsn['user']);
108 $this->password = ( ! isset($dsn['pass'])) ? '' : rawurldecode($dsn['pass']);
109 $this->database = ( ! isset($dsn['path'])) ? '' : rawurldecode(substr($dsn['path'], 1));
110 }
111
112 if ($this->pconnect == FALSE)
113 {
114 $this->conn_id = $this->db_connect();
115 }
116 else
117 {
118 $this->conn_id = $this->db_pconnect();
119 }
120
121 if ( ! $this->conn_id)
122 {
123 log_message('error', 'Unable to connect to the database');
124
125 if ($this->db_debug)
126 {
127 $this->display_error('db_unable_to_connect');
128 }
129 }
130 else
131 {
132 if ( ! $this->db_select())
133 {
134 log_message('error', 'Unable to select database: '.$this->database);
135
136 if ($this->db_debug)
137 {
138 $this->display_error('db_unable_to_select', $this->database);
139 }
140 }
141 }
142 }
143
144
145 // --------------------------------------------------------------------
146
147 /**
148 * Execute the query
149 *
150 * Accepts an SQL string as input and returns a result object upon
151 * successful execution of a "read" type query. Returns boolean TRUE
152 * upon successful execution of a "write" type query. Returns boolean
153 * FALSE upon failure, and if the $db_debug variable is set to TRUE
154 * will raise an error.
155 *
156 * @access public
157 * @param string An SQL query string
158 * @param array An array of binding data
159 * @return mixed
160 */
161 function query($sql, $binds = FALSE, $return_object = TRUE)
162 {
163 if ($sql == '')
164 {
165 if ($this->db_debug)
166 {
167 log_message('error', 'Invalid query: '.$sql);
168 return $this->display_error('db_invalid_query');
169 }
170 return FALSE;
171 }
172
173 // Compile binds if needed
174 if ($binds !== FALSE)
175 {
176 $sql = $this->compile_binds($sql, $binds);
177 }
178
179 // Save the query for debugging
180 $this->queries[] = $sql;
181
182 // Start the Query Timer
183 $time_start = list($sm, $ss) = explode(' ', microtime());
184
185 // Run the Query
186 if (FALSE === ($this->result_id = $this->simple_query($sql)))
187 {
188 // This will trigger a rollback if transactions are being used
189 $this->_trans_failure = TRUE;
190
191 if ($this->db_debug)
192 {
193 log_message('error', 'Query error: '.$this->error_message());
194 return $this->display_error(
195 array(
196 'Error Number: '.$this->error_number(),
197 $this->error_message(),
198 $sql
199 )
200 );
201 }
202
203 return FALSE;
204 }
205
206 // Stop and aggregate the query time results
207 $time_end = list($em, $es) = explode(' ', microtime());
208 $this->benchmark += ($em + $es) - ($sm + $ss);
209
210 // Increment the query counter
211 $this->query_count++;
212
213 // Was the query a "write" type?
214 // If so we'll simply return true
215 if ($this->is_write_type($sql) === TRUE)
216 {
217 return TRUE;
218 }
219
220 // Return TRUE if we don't need to create a result object
221 // Currently only the Oracle driver uses this when stored
222 // procedures are used
223 if ($return_object !== TRUE)
224 {
225 return TRUE;
226 }
227
228 // Define the result driver name
229 $result = 'CI_DB_'.$this->dbdriver.'_result';
230
231 // Load the result classes
232 if ( ! class_exists($result))
233 {
234 include_once(BASEPATH.'database/DB_result'.EXT);
235 include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result'.EXT);
236 }
237
238 // Instantiate the result object
239 $RES = new $result();
240 $RES->conn_id = $this->conn_id;
241 $RES->db_debug = $this->db_debug;
242 $RES->result_id = $this->result_id;
243
244 if ($this->dbdriver == 'oci8')
245 {
246 $RES->stmt_id = $this->stmt_id;
247 $RES->curs_id = NULL;
248 $RES->limit_used = $this->limit_used;
249 }
250
251 return $RES;
252 }
253
254 // --------------------------------------------------------------------
255
256 /**
257 * Simple Query
258 * This is a simiplified version of the query() function. Internally
259 * we only use it when running transaction commands since they do
260 * not require all the features of the main query() function.
261 *
262 * @access public
263 * @param string the sql query
264 * @return mixed
265 */
266 function simple_query($sql)
267 {
268 if ( ! $this->conn_id)
269 {
270 $this->initialize();
271 }
272
273 return $this->_execute($sql, $this->conn_id);
274 }
275
276 // --------------------------------------------------------------------
277
278 /**
279 * Disable Transactions
280 * This permits transactions to be disabled at run-time.
281 *
282 * @access public
283 * @return void
284 */
285 function trans_off()
286 {
287 $this->trans_enabled = FALSE;
288 }
289
290 // --------------------------------------------------------------------
291
292 /**
293 * Start Transaction
294 *
295 * @access public
296 * @return void
297 */
298 function trans_start($test_mode = FALSE)
299 {
300 if ( ! $this->trans_enabled)
301 {
302 return FALSE;
303 }
304
305 // When transactions are nested we only begin/commit/rollback the outermost ones
306 if ($this->_trans_depth > 0)
307 {
308 $this->_trans_depth += 1;
309 return;
310 }
311
312 $this->trans_begin($test_mode);
313 }
314
315 // --------------------------------------------------------------------
316
317 /**
318 * Complete Transaction
319 *
320 * @access public
321 * @return bool
322 */
323 function trans_complete()
324 {
325 if ( ! $this->trans_enabled)
326 {
327 return FALSE;
328 }
329
330 // When transactions are nested we only begin/commit/rollback the outermost ones
331 if ($this->_trans_depth > 1)
332 {
333 $this->_trans_depth -= 1;
334 return TRUE;
335 }
336
337 // The query() function will set this flag to TRUE in the event that a query failed
338 if ($this->_trans_failure === TRUE)
339 {
340 $this->trans_rollback();
341
342 if ($this->db_debug)
343 {
344 return $this->display_error('db_transaction_failure');
345 }
346 return FALSE;
347 }
348
349 $this->trans_commit();
350 return TRUE;
351 }
352
353 // --------------------------------------------------------------------
354
355 /**
356 * Lets you retrieve the transaction flag to determine if it has failed
357 *
358 * @access public
359 * @return bool
360 */
361 function trans_status()
362 {
363 return $this->_trans_failure;
364 }
365
366 // --------------------------------------------------------------------
367
368 /**
369 * Compile Bindings
370 *
371 * @access public
372 * @param string the sql statement
373 * @param array an array of bind data
374 * @return string
375 */
376 function compile_binds($sql, $binds)
377 {
378 if (FALSE === strpos($sql, $this->bind_marker))
379 {
380 return $sql;
381 }
382
383 if ( ! is_array($binds))
384 {
385 $binds = array($binds);
386 }
387
388 foreach ($binds as $val)
389 {
390 $val = $this->escape($val);
391
392 // Just in case the replacement string contains the bind
393 // character we'll temporarily replace it with a marker
394 $val = str_replace($this->bind_marker, '{%bind_marker%}', $val);
395 $sql = preg_replace("#".preg_quote($this->bind_marker, '#')."#", str_replace('$', '\$', $val), $sql, 1);
396 }
397
398 return str_replace('{%bind_marker%}', $this->bind_marker, $sql);
399 }
400
401 // --------------------------------------------------------------------
402
403 /**
404 * Determines if a query is a "write" type.
405 *
406 * @access public
407 * @param string An SQL query string
408 * @return boolean
409 */
410 function is_write_type($sql)
411 {
412 if ( ! preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
413 {
414 return FALSE;
415 }
416 return TRUE;
417 }
418
419 // --------------------------------------------------------------------
420
421 /**
422 * Calculate the aggregate query elapsed time
423 *
424 * @access public
425 * @param intiger The number of decimal places
426 * @return integer
427 */
428 function elapsed_time($decimals = 6)
429 {
430 return number_format($this->benchmark, $decimals);
431 }
432
433 // --------------------------------------------------------------------
434
435 /**
436 * Returns the total number of queries
437 *
438 * @access public
439 * @return integer
440 */
441 function total_queries()
442 {
443 return $this->query_count;
444 }
445
446 // --------------------------------------------------------------------
447
448 /**
449 * Returns the last query that was executed
450 *
451 * @access public
452 * @return void
453 */
454 function last_query()
455 {
456 return end($this->queries);
457 }
458
459 // --------------------------------------------------------------------
460
461 /**
462 * "Smart" Escape String
463 *
464 * Escapes data based on type
465 * Sets boolean and null types
466 *
467 * @access public
468 * @param string
469 * @return integer
470 */
471 function escape($str)
472 {
473 switch (gettype($str))
474 {
475 case 'string' : $str = "'".$this->escape_str($str)."'";
476 break;
477 case 'boolean' : $str = ($str === FALSE) ? 0 : 1;
478 break;
479 default : $str = ($str === NULL) ? 'NULL' : $str;
480 break;
481 }
482
483 return $str;
484 }
485
486 // --------------------------------------------------------------------
487
488 /**
489 * Enables a native PHP function to be run, using a platform agnostic wrapper.
490 *
491 * @access public
492 * @param string the function name
493 * @param mixed any parameters needed by the function
494 * @return mixed
495 */
496 function call_function($function)
497 {
498 $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_';
499
500 if (FALSE === strpos($driver, $function))
501 {
502 $function = $driver.$function;
503 }
504
505 if ( ! function_exists($function))
506 {
507 if ($this->db_debug)
508 {
509 return $this->display_error('db_unsupported_function');
510 }
511 return FALSE;
512 }
513 else
514 {
515 $args = (func_num_args() > 1) ? array_shift(func_get_args()) : null;
516
517 return call_user_func_array($function, $args);
518 }
519 }
520
521 // --------------------------------------------------------------------
522
523 /**
524 * Close DB Connection
525 *
526 * @access public
527 * @return void
528 */
529 function close()
530 {
531 if (is_resource($this->conn_id))
532 {
533 $this->_close($this->conn_id);
534 }
535 $this->conn_id = FALSE;
536 }
537
538 // --------------------------------------------------------------------
539
540 /**
541 * Display an error message
542 *
543 * @access public
544 * @param string the error message
545 * @param string any "swap" values
546 * @param boolean whether to localize the message
547 * @return string sends the application/errror_db.php template
548 */
549 function display_error($error = '', $swap = '', $native = FALSE)
550 {
551 $LANG = new CI_Language();
552 $LANG->load('db');
553
554 $heading = 'MySQL Error';
555
556 if ($native == TRUE)
557 {
558 $message = $error;
559 }
560 else
561 {
562 $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
563 }
564
565 if ( ! class_exists('CI_Exceptions'))
566 {
567 include_once(BASEPATH.'libraries/Exceptions'.EXT);
568 }
569
570 $error = new CI_Exceptions();
571 echo $error->show_error('An Error Was Encountered', $message, 'error_db');
572 exit;
573
574 }
575
576}
577
578?>