blob: 2d1232d7b88c27049c4f72ce66a0ebbc9bcdfe1c [file] [log] [blame]
adminb0dd10f2006-08-25 17:25:49 +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/libraries/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 = '';
admin2ed76d52006-09-02 17:34:52 +000039 var $port = '';
adminb0dd10f2006-08-25 17:25:49 +000040 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
admine348efb2006-09-20 21:13:26 +000049 // These are use with Oracle
50 var $stmt_id;
51 var $curs_id;
52 var $limit_used;
53
54
adminb0dd10f2006-08-25 17:25:49 +000055 /**
56 * Constructor. Accepts one parameter containing the database
57 * connection settings.
58 *
59 * Database settings can be passed as discreet
60 * parameters or as a data source name in the first
61 * parameter. DSNs must have this prototype:
62 * $dsn = 'driver://username:password@hostname/database';
63 *
64 * @param mixed. Can be an array or a DSN string
65 */
66 function CI_DB_driver($params)
67 {
68 $this->initialize($params);
69 log_message('debug', 'Database Driver Class Initialized');
70 }
71
72 // --------------------------------------------------------------------
73
74 /**
75 * Initialize Database Settings
76 *
77 * @access private Called by the constructor
78 * @param mixed
79 * @return void
80 */
81 function initialize($params = '')
82 {
83 if (is_array($params))
84 {
admin2ed76d52006-09-02 17:34:52 +000085 foreach (array('hostname' => '', 'username' => '', 'password' => '', 'database' => '', 'dbdriver' => 'mysql', 'dbprefix' => '', 'port' => '', 'pconnect' => FALSE, 'db_debug' => FALSE) as $key => $val)
adminb0dd10f2006-08-25 17:25:49 +000086 {
87 $this->$key = ( ! isset($params[$key])) ? $val : $params[$key];
88 }
89 }
90 elseif (strpos($params, '://'))
91 {
92 if (FALSE === ($dsn = @parse_url($params)))
93 {
94 log_message('error', 'Invalid DB Connection String');
95
admin57b3d392006-08-27 15:28:31 +000096 if ($this->db_debug)
adminb0dd10f2006-08-25 17:25:49 +000097 {
98 return $this->display_error('db_invalid_connection_str');
99 }
100 return FALSE;
101 }
102
103 $this->hostname = ( ! isset($dsn['host'])) ? '' : rawurldecode($dsn['host']);
104 $this->username = ( ! isset($dsn['user'])) ? '' : rawurldecode($dsn['user']);
105 $this->password = ( ! isset($dsn['pass'])) ? '' : rawurldecode($dsn['pass']);
106 $this->database = ( ! isset($dsn['path'])) ? '' : rawurldecode(substr($dsn['path'], 1));
107 }
108
109 if ($this->pconnect == FALSE)
110 {
111 $this->conn_id = $this->db_connect();
112 }
113 else
114 {
115 $this->conn_id = $this->db_pconnect();
116 }
117
118 if ( ! $this->conn_id)
119 {
120 log_message('error', 'Unable to connect to the database');
121
122 if ($this->db_debug)
123 {
124 $this->display_error('db_unable_to_connect');
125 }
126 }
127 else
128 {
129 if ( ! $this->db_select())
130 {
131 log_message('error', 'Unable to select database: '.$this->database);
132
133 if ($this->db_debug)
134 {
135 $this->display_error('db_unable_to_select', $this->database);
136 }
137 }
138 }
139 }
140
141 // --------------------------------------------------------------------
142
143 /**
144 * Database Version Number. Returns a string containing the
145 * version of the database being used
146 *
147 * @access public
148 * @return string
149 */
150 function version()
151 {
152 if (FALSE === ($sql = $this->_version()))
153 {
154 if ($this->db_debug)
155 {
156 return $this->display_error('db_unsupported_function');
157 }
158 return FALSE;
159 }
admine348efb2006-09-20 21:13:26 +0000160
161 if ($this->dbdriver == 'oci8')
162 {
163 return $sql;
164 }
adminb0dd10f2006-08-25 17:25:49 +0000165
166 $query = $this->query($sql);
167 $row = $query->row();
168 return $row->ver;
169 }
170
171 // --------------------------------------------------------------------
172
173 /**
174 * Execute the query
175 *
176 * Accepts an SQL string as input and returns a result object upon
177 * successful execution of a "read" type query. Returns boolean TRUE
178 * upon successful execution of a "write" type query. Returns boolean
179 * FALSE upon failure, and if the $db_debug variable is set to TRUE
180 * will raise an error.
181 *
182 * @access public
183 * @param string An SQL query string
184 * @param array An array of binding data
185 * @return mixed
186 */
admine348efb2006-09-20 21:13:26 +0000187 function query($sql, $binds = FALSE, $return_object = TRUE)
adminb0dd10f2006-08-25 17:25:49 +0000188 {
189 if ( ! $this->conn_id)
190 {
191 $this->initialize();
192 }
193
194 if ($sql == '')
195 {
196 if ($this->db_debug)
197 {
198 log_message('error', 'Invalid query: '.$sql);
199 return $this->display_error('db_invalid_query');
200 }
201 return FALSE;
202 }
203
204 // Compile binds if needed
205 if ($binds !== FALSE)
206 {
207 $sql = $this->compile_binds($sql, $binds);
208 }
209
210 // Start the Query Timer
211 $time_start = list($sm, $ss) = explode(' ', microtime());
212
213 // Save the query for debugging
214 $this->queries[] = $sql;
215
216 // Run the Query
217 if (FALSE === ($this->result_id = $this->execute($sql, $this->conn_id)))
218 {
219 if ($this->db_debug)
220 {
221 log_message('error', 'Query error: '.$this->error_message());
222 return $this->display_error(
223 array(
224 'Error Number: '.$this->error_number(),
225 $this->error_message(),
226 $sql
227 )
228 );
229 }
230
231 return FALSE;
232 }
233
234 // Stop and aggregate the query time results
235 $time_end = list($em, $es) = explode(' ', microtime());
236 $this->benchmark += ($em + $es) - ($sm + $ss);
237
238 // Increment the query counter
239 $this->query_count++;
240
241 // Was the query a "write" type?
admine348efb2006-09-20 21:13:26 +0000242 // If so we'll simply return true
adminb0dd10f2006-08-25 17:25:49 +0000243 if ($this->is_write_type($sql) === TRUE)
244 {
245 return TRUE;
246 }
admine348efb2006-09-20 21:13:26 +0000247
248 // Return TRUE if we don't need to create a result object
249 // Currently only the Oracle driver uses this when stored
250 // procedures are used
251 if ($return_object !== TRUE)
252 {
253 return TRUE;
254 }
adminb0dd10f2006-08-25 17:25:49 +0000255
256 // Instantiate and return the DB result object
257 $result = 'CI_DB_'.$this->dbdriver.'_result';
258
259 $RES = new $result();
260 $RES->conn_id = $this->conn_id;
261 $RES->db_debug = $this->db_debug;
262 $RES->result_id = $this->result_id;
admine348efb2006-09-20 21:13:26 +0000263
264 if ($this->dbdriver == 'oci8')
265 {
266 $RES->stmt_id = $this->stmt_id;
267 $RES->curs_id = NULL;
268 $RES->limit_used = $this->limit_used;
269 }
adminb0dd10f2006-08-25 17:25:49 +0000270
271 return $RES;
272 }
273
274 // --------------------------------------------------------------------
275
276 /**
277 * Enables a native PHP function to be run, using a platform agnostic wrapper.
278 *
279 * @access public
280 * @param string the function name
281 * @param mixed any parameters needed by the function
282 * @return mixed
283 */
284 function call_function($function)
285 {
286 $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_';
287
288 if (FALSE === strpos($driver, $function))
289 {
290 $function = $driver.$function;
291 }
292
293 if ( ! function_exists($function))
294 {
admin57b3d392006-08-27 15:28:31 +0000295 if ($this->db_debug)
adminb0dd10f2006-08-25 17:25:49 +0000296 {
297 return $this->display_error('db_unsupported_function');
298 }
299 return FALSE;
300 }
301 else
302 {
303 $args = (func_num_args() > 1) ? array_shift(func_get_args()) : null;
304
305 return call_user_func_array($function, $args);
306 }
307 }
308
309 // --------------------------------------------------------------------
310
311 /**
312 * Determines if a query is a "write" type.
313 *
314 * @access public
315 * @param string An SQL query string
316 * @return boolean
317 */
318 function is_write_type($sql)
319 {
320 if ( ! preg_match('/^\s*"?(INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
321 {
322 return FALSE;
323 }
324 return TRUE;
325 }
326
327 // --------------------------------------------------------------------
328
329 /**
330 * Calculate the aggregate query elapsed time
331 *
332 * @access public
333 * @param intiger The number of decimal places
334 * @return integer
335 */
336 function elapsed_time($decimals = 6)
337 {
338 return number_format($this->benchmark, $decimals);
339 }
340
341 // --------------------------------------------------------------------
342
343 /**
344 * Returns the total number of queries
345 *
346 * @access public
347 * @return integer
348 */
349 function total_queries()
350 {
351 return $this->query_count;
352 }
353
354 // --------------------------------------------------------------------
355
356 /**
357 * Returns the last query that was executed
358 *
359 * @access public
360 * @return void
361 */
362 function last_query()
363 {
364 return end($this->queries);
365 }
366
367 // --------------------------------------------------------------------
368
369 /**
370 * "Smart" Escape String
371 *
372 * Escapes data based on type
373 * Sets boolean and null types
374 *
375 * @access public
376 * @param string
377 * @return integer
378 */
379 function escape($str)
380 {
admine348efb2006-09-20 21:13:26 +0000381 switch (gettype($str))
adminb0dd10f2006-08-25 17:25:49 +0000382 {
admine348efb2006-09-20 21:13:26 +0000383 case 'string' : $str = "'".$this->escape_str($str)."'";
384 break;
385 case 'boolean' : $str = ($str === FALSE) ? 0 : 1;
386 break;
387 default : $str = ($str === NULL) ? 'NULL' : $str;
388 break;
389 }
390
adminb0dd10f2006-08-25 17:25:49 +0000391 return $str;
392 }
393
394 // --------------------------------------------------------------------
395
396 /**
397 * Returns an array of table names
398 *
399 * @access public
400 * @return array
401 */
402 function tables()
403 {
404 if (FALSE === ($sql = $this->_show_tables()))
405 {
406 if ($this->db_debug)
407 {
408 return $this->display_error('db_unsupported_function');
409 }
410 return FALSE;
411 }
412
413 $retval = array();
414 $query = $this->query($sql);
415
416 if ($query->num_rows() > 0)
417 {
418 foreach($query->result_array() as $row)
419 {
admine348efb2006-09-20 21:13:26 +0000420 if (isset($row['TABLE_NAME']))
421 {
422 $retval[] = $row['TABLE_NAME'];
423 }
424 else
425 {
426 $retval[] = array_shift($row);
427 }
adminb0dd10f2006-08-25 17:25:49 +0000428 }
429 }
430
431 return $retval;
432 }
433
434 // --------------------------------------------------------------------
435
436 /**
437 * Determine if a particular table exists
438 * @access public
439 * @return boolean
440 */
441 function table_exists($table_name)
442 {
443 return ( ! in_array($this->dbprefix.$table_name, $this->tables())) ? FALSE : TRUE;
444 }
445
446 // --------------------------------------------------------------------
447
448 /**
449 * Fetch MySQL Field Names
450 *
451 * @access public
452 * @param string the table name
453 * @return array
454 */
455 function field_names($table = '')
456 {
457 if ($table == '')
458 {
admin57b3d392006-08-27 15:28:31 +0000459 if ($this->db_debug)
adminb0dd10f2006-08-25 17:25:49 +0000460 {
461 return $this->display_error('db_field_param_missing');
462 }
463 return FALSE;
464 }
465
466 if (FALSE === ($sql = $this->_show_columns($this->dbprefix.$table)))
467 {
468 if ($this->db_debug)
469 {
470 return $this->display_error('db_unsupported_function');
471 }
472 return FALSE;
473 }
474
475 $query = $this->query($sql);
476
477 $retval = array();
478 foreach($query->result_array() as $row)
479 {
admine348efb2006-09-20 21:13:26 +0000480 if (isset($row['COLUMN_NAME']))
adminb0dd10f2006-08-25 17:25:49 +0000481 {
482 $retval[] = $row['COLUMN_NAME'];
483 }
484 else
485 {
486 $retval[] = current($row);
487 }
488 }
489
490 return $retval;
491 }
492
493 // --------------------------------------------------------------------
494
495 /**
496 * Returns an object with field data
497 *
498 * @access public
499 * @param string the table name
500 * @return object
501 */
502 function field_data($table = '')
503 {
504 if ($table == '')
505 {
admin57b3d392006-08-27 15:28:31 +0000506 if ($this->db_debug)
adminb0dd10f2006-08-25 17:25:49 +0000507 {
508 return $this->display_error('db_field_param_missing');
509 }
510 return FALSE;
511 }
512
513 return $this->_field_data($this->dbprefix.$table);
514 }
515
516 // --------------------------------------------------------------------
517
518 /**
519 * Primary
520 *
521 * Retrieves the primary key. It assumes that the row in the first
522 * position is the primary key
523 *
524 * @access public
525 * @param string the table name
526 * @return string
527 */
528 function primary($table = '')
529 {
530 $fields = $this->field_names($table);
531
532 if ( ! is_array($fields))
533 {
534 return FALSE;
535 }
536
537 return current($fields);
538 }
539
540 // --------------------------------------------------------------------
541
542 /**
543 * Compile Bindings
544 *
545 * @access public
546 * @param string the sql statement
547 * @param array an array of bind data
548 * @return string
549 */
550 function compile_binds($sql, $binds)
551 {
552 if (FALSE === strpos($sql, $this->bind_marker))
553 {
554 return $sql;
555 }
556
557 if ( ! is_array($binds))
558 {
559 $binds = array($binds);
560 }
561
562 foreach ($binds as $val)
563 {
564 $val = $this->escape($val);
565
566 // Just in case the replacement string contains the bind
567 // character we'll temporarily replace it with a marker
568 $val = str_replace($this->bind_marker, '{%bind_marker%}', $val);
admin57b3d392006-08-27 15:28:31 +0000569 $sql = preg_replace("#".preg_quote($this->bind_marker, '#')."#", str_replace('$', '\$', $val), $sql, 1);
adminb0dd10f2006-08-25 17:25:49 +0000570 }
571
572 return str_replace('{%bind_marker%}', $this->bind_marker, $sql);
573 }
574
575 // --------------------------------------------------------------------
576
577 /**
578 * Generate an insert string
579 *
580 * @access public
581 * @param string the table upon which the query will be performed
582 * @param array an associative array data of key/values
583 * @return string
584 */
585 function insert_string($table, $data)
586 {
587 $fields = array();
588 $values = array();
589
590 foreach($data as $key => $val)
591 {
592 $fields[] = $key;
593 $values[] = $this->escape($val);
594 }
595
596 return $this->_insert($this->dbprefix.$table, $fields, $values);
597 }
598
599 // --------------------------------------------------------------------
600
601 /**
602 * Generate an update string
603 *
604 * @access public
605 * @param string the table upon which the query will be performed
606 * @param array an associative array data of key/values
607 * @param mixed the "where" statement
608 * @return string
609 */
610 function update_string($table, $data, $where)
611 {
612 if ($where == '')
613 return false;
614
615 $fields = array();
616 foreach($data as $key => $val)
617 {
618 $fields[$key] = $this->escape($val);
619 }
620
621 if ( ! is_array($where))
622 {
623 $dest = array($where);
624 }
625 else
626 {
627 $dest = array();
628 foreach ($where as $key => $val)
629 {
630 $prefix = (count($dest) == 0) ? '' : ' AND ';
631
632 if ($val != '')
633 {
634 if ( ! $this->_has_operator($key))
635 {
636 $key .= ' =';
637 }
638
639 $val = ' '.$this->escape($val);
640 }
641
642 $dest[] = $prefix.$key.$val;
643 }
644 }
645
646 return $this->_update($this->dbprefix.$table, $fields, $dest);
647 }
648
649 // --------------------------------------------------------------------
650
651 /**
652 * Close DB Connection
653 *
654 * @access public
655 * @return void
656 */
657 function close()
658 {
659 if (is_resource($this->conn_id))
660 {
661 $this->destroy($this->conn_id);
662 }
663 $this->conn_id = FALSE;
664 }
665
666 // --------------------------------------------------------------------
667
668 /**
669 * Display an error message
670 *
671 * @access public
672 * @param string the error message
673 * @param string any "swap" values
674 * @param boolean whether to localize the message
675 * @return string sends the application/errror_db.php template
676 */
677 function display_error($error = '', $swap = '', $native = FALSE)
678 {
679 $LANG = new CI_Language();
680 $LANG->load('db');
681
682 $heading = 'MySQL Error';
683
684 if ($native == TRUE)
685 {
686 $message = $error;
687 }
688 else
689 {
690 $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
691 }
692
693 if ( ! class_exists('CI_Exceptions'))
694 {
695 include_once(BASEPATH.'libraries/Exceptions.php');
696 }
697
698 $error = new CI_Exceptions();
699 echo $error->show_error('An Error Was Encountered', $message, 'error_db');
700 exit;
701
702 }
703
adminb0dd10f2006-08-25 17:25:49 +0000704}
705
706
707/**
708 * Database Result Class
709 *
710 * This is the platform-independent result class.
711 * This class will not be called directly. Rather, the adapter
712 * class for the specific database will extend and instantiate it.
713 *
714 * @category Database
715 * @author Rick Ellis
716 * @link http://www.codeigniter.com/user_guide/libraries/database/
717 */
718class CI_DB_result {
719
720 var $conn_id = FALSE;
721 var $result_id = FALSE;
722 var $db_debug = FALSE;
723 var $result_array = array();
724 var $result_object = array();
725 var $current_row = 0;
726
727 /**
728 * Query result. Acts as a wrapper function for the following functions.
729 *
730 * @access public
731 * @param string can be "object" or "array"
732 * @return mixed either a result object or array
733 */
734 function result($type = 'object')
735 {
736 return ($type == 'object') ? $this->result_object() : $this->result_array();
737 }
738
739 // --------------------------------------------------------------------
740
741 /**
742 * Query result. "object" version.
743 *
744 * @access public
745 * @return object
746 */
747 function result_object()
748 {
749 if (count($this->result_object) > 0)
750 {
751 return $this->result_object;
752 }
753
754 while ($row = $this->_fetch_object())
755 {
756 $this->result_object[] = $row;
757 }
758
759 if (count($this->result_object) == 0)
760 {
761 return FALSE;
762 }
763
764 return $this->result_object;
765 }
766
767 // --------------------------------------------------------------------
768
769 /**
770 * Query result. "array" version.
771 *
772 * @access public
773 * @return array
774 */
775 function result_array()
776 {
777 if (count($this->result_array) > 0)
778 {
779 return $this->result_array;
780 }
781
782 while ($row = $this->_fetch_assoc())
783 {
784 $this->result_array[] = $row;
785 }
786
787 if (count($this->result_array) == 0)
788 {
789 return FALSE;
790 }
791
792 return $this->result_array;
793 }
794
795 // --------------------------------------------------------------------
796
797 /**
798 * Query result. Acts as a wrapper function for the following functions.
799 *
800 * @access public
801 * @param string can be "object" or "array"
802 * @return mixed either a result object or array
803 */
804 function row($n = 0, $type = 'object')
805 {
806 return ($type == 'object') ? $this->row_object($n) : $this->row_array($n);
807 }
808
809 // --------------------------------------------------------------------
810
811 /**
812 * Returns a single result row - object version
813 *
814 * @access public
815 * @return object
816 */
817 function row_object($n = 0)
818 {
819 if (FALSE === ($result = $this->result_object()))
820 {
821 return FALSE;
822 }
823
824 if ($n != $this->current_row AND isset($result[$n]))
825 {
826 $this->current_row = $n;
827 }
828
829 return $result[$this->current_row];
830 }
831
832 // --------------------------------------------------------------------
833
834 /**
835 * Returns a single result row - array version
836 *
837 * @access public
838 * @return array
839 */
840 function row_array($n = 0)
841 {
842 if (FALSE === ($result = $this->result_array()))
843 {
844 return FALSE;
845 }
846
847 if ($n != $this->current_row AND isset($result[$n]))
848 {
849 $this->current_row = $n;
850 }
851
852 return $result[$this->current_row];
853 }
854
855 // --------------------------------------------------------------------
856
857 /**
858 * Returns the "next" row
859 *
860 * @access public
861 * @return object
862 */
863 function next_row($type = 'object')
864 {
865 if (FALSE === ($result = $this->result($type)))
866 {
867 return FALSE;
868 }
869
870 if (isset($result[$this->current_row + 1]))
871 {
872 ++$this->current_row;
873 }
874
875 return $result[$this->current_row];
876 }
877
878 // --------------------------------------------------------------------
879
880 /**
881 * Returns the "previous" row
882 *
883 * @access public
884 * @return object
885 */
886 function previous_row($type = 'object')
887 {
888 if (FALSE === ($result = $this->result($type)))
889 {
890 return FALSE;
891 }
892
893 if (isset($result[$this->current_row - 1]))
894 {
895 --$this->current_row;
896 }
897 return $result[$this->current_row];
898 }
899
900 // --------------------------------------------------------------------
901
902 /**
903 * Returns the "first" row
904 *
905 * @access public
906 * @return object
907 */
908 function first_row($type = 'object')
909 {
910 if (FALSE === ($result = $this->result($type)))
911 {
912 return FALSE;
913 }
914 return $result[0];
915 }
916
917 // --------------------------------------------------------------------
918
919 /**
920 * Returns the "last" row
921 *
922 * @access public
923 * @return object
924 */
925 function last_row($type = 'object')
926 {
927 if (FALSE === ($result = $this->result($type)))
928 {
929 return FALSE;
930 }
931 return $result[count($result) -1];
932 }
933
934}
935
adminb0dd10f2006-08-25 17:25:49 +0000936?>