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