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