blob: d25135ea66b82ada5ba9ac2491057dd37651e5c5 [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 /**
admin9cd4e8e2006-09-25 23:26:25 +0000148 * The name of the platform in use (mysql, mssql, etc...)
149 *
150 * @access public
151 * @return string
152 */
153 function platform()
154 {
155 return $this->dbdriver;
156 }
157
158 // --------------------------------------------------------------------
159
160 /**
161 * Database Version Number. Returns a string containing the
162 * version of the database being used
163 *
164 * @access public
165 * @return string
166 */
167 function version()
168 {
169 if (FALSE === ($sql = $this->_version()))
170 {
171 if ($this->db_debug)
172 {
173 return $this->display_error('db_unsupported_function');
174 }
175 return FALSE;
176 }
177
178 if ($this->dbdriver == 'oci8')
179 {
180 return $sql;
181 }
182
183 $query = $this->query($sql);
184 $row = $query->row();
185 return $row->ver;
186 }
187
188 // --------------------------------------------------------------------
189
190 /**
admin7b613c72006-09-24 18:05:17 +0000191 * Execute the query
192 *
193 * Accepts an SQL string as input and returns a result object upon
194 * successful execution of a "read" type query. Returns boolean TRUE
195 * upon successful execution of a "write" type query. Returns boolean
196 * FALSE upon failure, and if the $db_debug variable is set to TRUE
197 * will raise an error.
198 *
199 * @access public
200 * @param string An SQL query string
201 * @param array An array of binding data
202 * @return mixed
203 */
204 function query($sql, $binds = FALSE, $return_object = TRUE)
205 {
206 if ($sql == '')
207 {
208 if ($this->db_debug)
209 {
210 log_message('error', 'Invalid query: '.$sql);
211 return $this->display_error('db_invalid_query');
212 }
213 return FALSE;
214 }
215
216 // Compile binds if needed
217 if ($binds !== FALSE)
218 {
219 $sql = $this->compile_binds($sql, $binds);
220 }
221
222 // Save the query for debugging
223 $this->queries[] = $sql;
224
225 // Start the Query Timer
226 $time_start = list($sm, $ss) = explode(' ', microtime());
227
228 // Run the Query
229 if (FALSE === ($this->result_id = $this->simple_query($sql)))
230 {
231 // This will trigger a rollback if transactions are being used
232 $this->_trans_failure = TRUE;
233
234 if ($this->db_debug)
235 {
adminbb1d4392006-09-24 20:14:38 +0000236 log_message('error', 'Query error: '.$this->_error_message());
admin7b613c72006-09-24 18:05:17 +0000237 return $this->display_error(
238 array(
adminbb1d4392006-09-24 20:14:38 +0000239 'Error Number: '.$this->_error_number(),
240 $this->_error_message(),
admin7b613c72006-09-24 18:05:17 +0000241 $sql
242 )
243 );
244 }
245
246 return FALSE;
247 }
248
249 // Stop and aggregate the query time results
250 $time_end = list($em, $es) = explode(' ', microtime());
251 $this->benchmark += ($em + $es) - ($sm + $ss);
252
253 // Increment the query counter
254 $this->query_count++;
255
256 // Was the query a "write" type?
257 // If so we'll simply return true
258 if ($this->is_write_type($sql) === TRUE)
259 {
260 return TRUE;
261 }
262
263 // Return TRUE if we don't need to create a result object
264 // Currently only the Oracle driver uses this when stored
265 // procedures are used
266 if ($return_object !== TRUE)
267 {
268 return TRUE;
269 }
270
271 // Define the result driver name
272 $result = 'CI_DB_'.$this->dbdriver.'_result';
273
274 // Load the result classes
275 if ( ! class_exists($result))
276 {
277 include_once(BASEPATH.'database/DB_result'.EXT);
278 include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result'.EXT);
279 }
280
281 // Instantiate the result object
282 $RES = new $result();
283 $RES->conn_id = $this->conn_id;
284 $RES->db_debug = $this->db_debug;
285 $RES->result_id = $this->result_id;
286
287 if ($this->dbdriver == 'oci8')
288 {
289 $RES->stmt_id = $this->stmt_id;
290 $RES->curs_id = NULL;
291 $RES->limit_used = $this->limit_used;
292 }
293
294 return $RES;
295 }
296
297 // --------------------------------------------------------------------
298
299 /**
300 * Simple Query
301 * This is a simiplified version of the query() function. Internally
302 * we only use it when running transaction commands since they do
303 * not require all the features of the main query() function.
304 *
305 * @access public
306 * @param string the sql query
307 * @return mixed
308 */
309 function simple_query($sql)
310 {
311 if ( ! $this->conn_id)
312 {
313 $this->initialize();
314 }
315
316 return $this->_execute($sql, $this->conn_id);
317 }
318
319 // --------------------------------------------------------------------
320
321 /**
322 * Disable Transactions
323 * This permits transactions to be disabled at run-time.
324 *
325 * @access public
326 * @return void
327 */
328 function trans_off()
329 {
330 $this->trans_enabled = FALSE;
331 }
332
333 // --------------------------------------------------------------------
334
335 /**
336 * Start Transaction
337 *
338 * @access public
339 * @return void
340 */
341 function trans_start($test_mode = FALSE)
342 {
343 if ( ! $this->trans_enabled)
344 {
345 return FALSE;
346 }
347
348 // When transactions are nested we only begin/commit/rollback the outermost ones
349 if ($this->_trans_depth > 0)
350 {
351 $this->_trans_depth += 1;
352 return;
353 }
354
355 $this->trans_begin($test_mode);
356 }
357
358 // --------------------------------------------------------------------
359
360 /**
361 * Complete Transaction
362 *
363 * @access public
364 * @return bool
365 */
366 function trans_complete()
367 {
368 if ( ! $this->trans_enabled)
369 {
370 return FALSE;
371 }
372
373 // When transactions are nested we only begin/commit/rollback the outermost ones
374 if ($this->_trans_depth > 1)
375 {
376 $this->_trans_depth -= 1;
377 return TRUE;
378 }
379
380 // The query() function will set this flag to TRUE in the event that a query failed
381 if ($this->_trans_failure === TRUE)
382 {
383 $this->trans_rollback();
384
385 if ($this->db_debug)
386 {
387 return $this->display_error('db_transaction_failure');
388 }
389 return FALSE;
390 }
391
392 $this->trans_commit();
393 return TRUE;
394 }
395
396 // --------------------------------------------------------------------
397
398 /**
399 * Lets you retrieve the transaction flag to determine if it has failed
400 *
401 * @access public
402 * @return bool
403 */
404 function trans_status()
405 {
406 return $this->_trans_failure;
407 }
408
409 // --------------------------------------------------------------------
410
411 /**
412 * Compile Bindings
413 *
414 * @access public
415 * @param string the sql statement
416 * @param array an array of bind data
417 * @return string
418 */
419 function compile_binds($sql, $binds)
420 {
421 if (FALSE === strpos($sql, $this->bind_marker))
422 {
423 return $sql;
424 }
425
426 if ( ! is_array($binds))
427 {
428 $binds = array($binds);
429 }
430
431 foreach ($binds as $val)
432 {
433 $val = $this->escape($val);
434
435 // Just in case the replacement string contains the bind
436 // character we'll temporarily replace it with a marker
437 $val = str_replace($this->bind_marker, '{%bind_marker%}', $val);
438 $sql = preg_replace("#".preg_quote($this->bind_marker, '#')."#", str_replace('$', '\$', $val), $sql, 1);
439 }
440
441 return str_replace('{%bind_marker%}', $this->bind_marker, $sql);
442 }
443
444 // --------------------------------------------------------------------
445
446 /**
447 * Determines if a query is a "write" type.
448 *
449 * @access public
450 * @param string An SQL query string
451 * @return boolean
452 */
453 function is_write_type($sql)
454 {
455 if ( ! preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
456 {
457 return FALSE;
458 }
459 return TRUE;
460 }
461
462 // --------------------------------------------------------------------
463
464 /**
465 * Calculate the aggregate query elapsed time
466 *
467 * @access public
468 * @param intiger The number of decimal places
469 * @return integer
470 */
471 function elapsed_time($decimals = 6)
472 {
473 return number_format($this->benchmark, $decimals);
474 }
475
476 // --------------------------------------------------------------------
477
478 /**
479 * Returns the total number of queries
480 *
481 * @access public
482 * @return integer
483 */
484 function total_queries()
485 {
486 return $this->query_count;
487 }
488
489 // --------------------------------------------------------------------
490
491 /**
492 * Returns the last query that was executed
493 *
494 * @access public
495 * @return void
496 */
497 function last_query()
498 {
499 return end($this->queries);
500 }
501
502 // --------------------------------------------------------------------
503
504 /**
505 * "Smart" Escape String
506 *
507 * Escapes data based on type
508 * Sets boolean and null types
509 *
510 * @access public
511 * @param string
512 * @return integer
513 */
514 function escape($str)
515 {
516 switch (gettype($str))
517 {
518 case 'string' : $str = "'".$this->escape_str($str)."'";
519 break;
520 case 'boolean' : $str = ($str === FALSE) ? 0 : 1;
521 break;
522 default : $str = ($str === NULL) ? 'NULL' : $str;
523 break;
524 }
525
526 return $str;
adminbb1d4392006-09-24 20:14:38 +0000527 }
528
admin9cd4e8e2006-09-25 23:26:25 +0000529
530
531 // --------------------------------------------------------------------
532
533 /**
534 * Primary
535 *
536 * Retrieves the primary key. It assumes that the row in the first
537 * position is the primary key
538 *
539 * @access public
540 * @param string the table name
541 * @return string
542 */
543 function primary($table = '')
544 {
545 $fields = $this->field_names($table);
546
547 if ( ! is_array($fields))
548 {
549 return FALSE;
550 }
551
552 return current($fields);
553 }
554
555 // --------------------------------------------------------------------
556
557 /**
558 * Fetch MySQL Field Names
559 *
560 * @access public
561 * @param string the table name
562 * @return array
563 */
564 function field_names($table = '')
565 {
566 if ($table == '')
567 {
568 if ($this->db_debug)
569 {
570 return $this->display_error('db_field_param_missing');
571 }
572 return FALSE;
573 }
574
575 if (FALSE === ($sql = $this->_list_columns($this->dbprefix.$table)))
576 {
577 if ($this->db_debug)
578 {
579 return $this->display_error('db_unsupported_function');
580 }
581 return FALSE;
582 }
583
584 $query = $this->query($sql);
585
586 $retval = array();
587 foreach($query->result_array() as $row)
588 {
589 if (isset($row['COLUMN_NAME']))
590 {
591 $retval[] = $row['COLUMN_NAME'];
592 }
593 else
594 {
595 $retval[] = current($row);
596 }
597 }
598
599 return $retval;
600 }
601
602 // --------------------------------------------------------------------
603
604 /**
605 * Returns an object with field data
606 *
607 * @access public
608 * @param string the table name
609 * @return object
610 */
611 function field_data($table = '')
612 {
613 if ($table == '')
614 {
615 if ($this->db_debug)
616 {
617 return $this->display_error('db_field_param_missing');
618 }
619 return FALSE;
620 }
621
622 $query = $this->query($this->_field_data($this->dbprefix.$table));
623 return $query->field_data();
624 }
625
adminbb1d4392006-09-24 20:14:38 +0000626 // --------------------------------------------------------------------
627
628 /**
629 * Generate an insert string
630 *
631 * @access public
632 * @param string the table upon which the query will be performed
633 * @param array an associative array data of key/values
634 * @return string
635 */
636 function insert_string($table, $data)
637 {
638 $fields = array();
639 $values = array();
640
641 foreach($data as $key => $val)
642 {
643 $fields[] = $key;
644 $values[] = $this->escape($val);
645 }
646
647 return $this->_insert($this->dbprefix.$table, $fields, $values);
648 }
649
650 // --------------------------------------------------------------------
651
652 /**
653 * Generate an update string
654 *
655 * @access public
656 * @param string the table upon which the query will be performed
657 * @param array an associative array data of key/values
658 * @param mixed the "where" statement
659 * @return string
660 */
661 function update_string($table, $data, $where)
662 {
663 if ($where == '')
664 return false;
665
666 $fields = array();
667 foreach($data as $key => $val)
668 {
669 $fields[$key] = $this->escape($val);
670 }
671
672 if ( ! is_array($where))
673 {
674 $dest = array($where);
675 }
676 else
677 {
678 $dest = array();
679 foreach ($where as $key => $val)
680 {
681 $prefix = (count($dest) == 0) ? '' : ' AND ';
682
683 if ($val != '')
684 {
685 if ( ! $this->_has_operator($key))
686 {
687 $key .= ' =';
688 }
689
690 $val = ' '.$this->escape($val);
691 }
692
693 $dest[] = $prefix.$key.$val;
694 }
695 }
696
697 return $this->_update($this->dbprefix.$table, $fields, $dest);
698 }
admin7b613c72006-09-24 18:05:17 +0000699
700 // --------------------------------------------------------------------
701
702 /**
703 * Enables a native PHP function to be run, using a platform agnostic wrapper.
704 *
705 * @access public
706 * @param string the function name
707 * @param mixed any parameters needed by the function
708 * @return mixed
709 */
710 function call_function($function)
711 {
712 $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_';
713
714 if (FALSE === strpos($driver, $function))
715 {
716 $function = $driver.$function;
717 }
718
719 if ( ! function_exists($function))
720 {
721 if ($this->db_debug)
722 {
723 return $this->display_error('db_unsupported_function');
724 }
725 return FALSE;
726 }
727 else
728 {
729 $args = (func_num_args() > 1) ? array_shift(func_get_args()) : null;
730
731 return call_user_func_array($function, $args);
732 }
733 }
734
735 // --------------------------------------------------------------------
736
737 /**
738 * Close DB Connection
739 *
740 * @access public
741 * @return void
742 */
743 function close()
744 {
745 if (is_resource($this->conn_id))
746 {
747 $this->_close($this->conn_id);
748 }
749 $this->conn_id = FALSE;
750 }
751
752 // --------------------------------------------------------------------
753
754 /**
755 * Display an error message
756 *
757 * @access public
758 * @param string the error message
759 * @param string any "swap" values
760 * @param boolean whether to localize the message
761 * @return string sends the application/errror_db.php template
762 */
763 function display_error($error = '', $swap = '', $native = FALSE)
764 {
765 $LANG = new CI_Language();
766 $LANG->load('db');
767
768 $heading = 'MySQL Error';
769
770 if ($native == TRUE)
771 {
772 $message = $error;
773 }
774 else
775 {
776 $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
777 }
778
779 if ( ! class_exists('CI_Exceptions'))
780 {
781 include_once(BASEPATH.'libraries/Exceptions'.EXT);
782 }
783
784 $error = new CI_Exceptions();
785 echo $error->show_error('An Error Was Encountered', $message, 'error_db');
786 exit;
787
788 }
789
790}
791
792?>