Remove file execution permission.
[living-lab-site.git] / system / database / DB_driver.php
1 <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2 /**
3  * CodeIgniter
4  *
5  * An open source application development framework for PHP 5.1.6 or newer
6  *
7  * @package             CodeIgniter
8  * @author              ExpressionEngine Dev Team
9  * @copyright   Copyright (c) 2008 - 2011, EllisLab, Inc.
10  * @license             http://codeigniter.com/user_guide/license.html
11  * @link                http://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              ExpressionEngine Dev Team
29  * @link                http://codeigniter.com/user_guide/database/
30  */
31 class 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 $char_set           = 'utf8';
40         var $dbcollat           = 'utf8_general_ci';
41         var $autoinit           = TRUE; // Whether to automatically initialize the DB
42         var $swap_pre           = '';
43         var $port                       = '';
44         var $pconnect           = FALSE;
45         var $conn_id            = FALSE;
46         var $result_id          = FALSE;
47         var $db_debug           = FALSE;
48         var $benchmark          = 0;
49         var $query_count        = 0;
50         var $bind_marker        = '?';
51         var $save_queries       = TRUE;
52         var $queries            = array();
53         var $query_times        = array();
54         var $data_cache         = array();
55         var $trans_enabled      = TRUE;
56         var $trans_strict       = TRUE;
57         var $_trans_depth       = 0;
58         var $_trans_status      = TRUE; // Used with transactions to determine if a rollback should occur
59         var $cache_on           = FALSE;
60         var $cachedir           = '';
61         var $cache_autodel      = FALSE;
62         var $CACHE; // The cache class object
63
64         // Private variables
65         var $_protect_identifiers       = TRUE;
66         var $_reserved_identifiers      = array('*'); // Identifiers that should NOT be escaped
67
68         // These are use with Oracle
69         var $stmt_id;
70         var $curs_id;
71         var $limit_used;
72
73
74
75         /**
76          * Constructor.  Accepts one parameter containing the database
77          * connection settings.
78          *
79          * @param array
80          */
81         function CI_DB_driver($params)
82         {
83                 if (is_array($params))
84                 {
85                         foreach ($params as $key => $val)
86                         {
87                                 $this->$key = $val;
88                         }
89                 }
90
91                 log_message('debug', 'Database Driver Class Initialized');
92         }
93
94         // --------------------------------------------------------------------
95
96         /**
97          * Initialize Database Settings
98          *
99          * @access      private Called by the constructor
100          * @param       mixed
101          * @return      void
102          */
103         function initialize()
104         {
105                 // If an existing connection resource is available
106                 // there is no need to connect and select the database
107                 if (is_resource($this->conn_id) OR is_object($this->conn_id))
108                 {
109                         return TRUE;
110                 }
111
112                 // ----------------------------------------------------------------
113
114                 // Connect to the database and set the connection ID
115                 $this->conn_id = ($this->pconnect == FALSE) ? $this->db_connect() : $this->db_pconnect();
116
117                 // No connection resource?  Throw an error
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                         return FALSE;
127                 }
128
129                 // ----------------------------------------------------------------
130
131                 // Select the DB... assuming a database name is specified in the config file
132                 if ($this->database != '')
133                 {
134                         if ( ! $this->db_select())
135                         {
136                                 log_message('error', 'Unable to select database: '.$this->database);
137
138                                 if ($this->db_debug)
139                                 {
140                                         $this->display_error('db_unable_to_select', $this->database);
141                                 }
142                                 return FALSE;
143                         }
144                         else
145                         {
146                                 // We've selected the DB. Now we set the character set
147                                 if ( ! $this->db_set_charset($this->char_set, $this->dbcollat))
148                                 {
149                                         return FALSE;
150                                 }
151
152                                 return TRUE;
153                         }
154                 }
155
156                 return TRUE;
157         }
158
159         // --------------------------------------------------------------------
160
161         /**
162          * Set client character set
163          *
164          * @access      public
165          * @param       string
166          * @param       string
167          * @return      resource
168          */
169         function db_set_charset($charset, $collation)
170         {
171                 if ( ! $this->_db_set_charset($this->char_set, $this->dbcollat))
172                 {
173                         log_message('error', 'Unable to set database connection charset: '.$this->char_set);
174
175                         if ($this->db_debug)
176                         {
177                                 $this->display_error('db_unable_to_set_charset', $this->char_set);
178                         }
179
180                         return FALSE;
181                 }
182
183                 return TRUE;
184         }
185
186         // --------------------------------------------------------------------
187
188         /**
189          * The name of the platform in use (mysql, mssql, etc...)
190          *
191          * @access      public
192          * @return      string
193          */
194         function platform()
195         {
196                 return $this->dbdriver;
197         }
198
199         // --------------------------------------------------------------------
200
201         /**
202          * Database Version Number.  Returns a string containing the
203          * version of the database being used
204          *
205          * @access      public
206          * @return      string
207          */
208         function version()
209         {
210                 if (FALSE === ($sql = $this->_version()))
211                 {
212                         if ($this->db_debug)
213                         {
214                                 return $this->display_error('db_unsupported_function');
215                         }
216                         return FALSE;
217                 }
218
219                 // Some DBs have functions that return the version, and don't run special
220                 // SQL queries per se. In these instances, just return the result.
221                 $driver_version_exceptions = array('oci8', 'sqlite');
222
223                 if (in_array($this->dbdriver, $driver_version_exceptions))
224                 {
225                         return $sql;
226                 }
227                 else
228                 {
229                         $query = $this->query($sql);
230                         return $query->row('ver');
231                 }
232         }
233
234         // --------------------------------------------------------------------
235
236         /**
237          * Execute the query
238          *
239          * Accepts an SQL string as input and returns a result object upon
240          * successful execution of a "read" type query.  Returns boolean TRUE
241          * upon successful execution of a "write" type query. Returns boolean
242          * FALSE upon failure, and if the $db_debug variable is set to TRUE
243          * will raise an error.
244          *
245          * @access      public
246          * @param       string  An SQL query string
247          * @param       array   An array of binding data
248          * @return      mixed
249          */
250         function query($sql, $binds = FALSE, $return_object = TRUE)
251         {
252                 if ($sql == '')
253                 {
254                         if ($this->db_debug)
255                         {
256                                 log_message('error', 'Invalid query: '.$sql);
257                                 return $this->display_error('db_invalid_query');
258                         }
259                         return FALSE;
260                 }
261
262                 // Verify table prefix and replace if necessary
263                 if ( ($this->dbprefix != '' AND $this->swap_pre != '') AND ($this->dbprefix != $this->swap_pre) )
264                 {
265                         $sql = preg_replace("/(\W)".$this->swap_pre."(\S+?)/", "\\1".$this->dbprefix."\\2", $sql);
266                 }
267
268                 // Is query caching enabled?  If the query is a "read type"
269                 // we will load the caching class and return the previously
270                 // cached query if it exists
271                 if ($this->cache_on == TRUE AND stristr($sql, 'SELECT'))
272                 {
273                         if ($this->_cache_init())
274                         {
275                                 $this->load_rdriver();
276                                 if (FALSE !== ($cache = $this->CACHE->read($sql)))
277                                 {
278                                         return $cache;
279                                 }
280                         }
281                 }
282
283                 // Compile binds if needed
284                 if ($binds !== FALSE)
285                 {
286                         $sql = $this->compile_binds($sql, $binds);
287                 }
288
289                 // Save the  query for debugging
290                 if ($this->save_queries == TRUE)
291                 {
292                         $this->queries[] = $sql;
293                 }
294
295                 // Start the Query Timer
296                 $time_start = list($sm, $ss) = explode(' ', microtime());
297
298                 // Run the Query
299                 if (FALSE === ($this->result_id = $this->simple_query($sql)))
300                 {
301                         if ($this->save_queries == TRUE)
302                         {
303                                 $this->query_times[] = 0;
304                         }
305
306                         // This will trigger a rollback if transactions are being used
307                         $this->_trans_status = FALSE;
308
309                         if ($this->db_debug)
310                         {
311                                 // grab the error number and message now, as we might run some
312                                 // additional queries before displaying the error
313                                 $error_no = $this->_error_number();
314                                 $error_msg = $this->_error_message();
315
316                                 // We call this function in order to roll-back queries
317                                 // if transactions are enabled.  If we don't call this here
318                                 // the error message will trigger an exit, causing the
319                                 // transactions to remain in limbo.
320                                 $this->trans_complete();
321
322                                 // Log and display errors
323                                 log_message('error', 'Query error: '.$error_msg);
324                                 return $this->display_error(
325                                                                                 array(
326                                                                                                 'Error Number: '.$error_no,
327                                                                                                 $error_msg,
328                                                                                                 $sql
329                                                                                         )
330                                                                                 );
331                         }
332
333                         return FALSE;
334                 }
335
336                 // Stop and aggregate the query time results
337                 $time_end = list($em, $es) = explode(' ', microtime());
338                 $this->benchmark += ($em + $es) - ($sm + $ss);
339
340                 if ($this->save_queries == TRUE)
341                 {
342                         $this->query_times[] = ($em + $es) - ($sm + $ss);
343                 }
344
345                 // Increment the query counter
346                 $this->query_count++;
347
348                 // Was the query a "write" type?
349                 // If so we'll simply return true
350                 if ($this->is_write_type($sql) === TRUE)
351                 {
352                         // If caching is enabled we'll auto-cleanup any
353                         // existing files related to this particular URI
354                         if ($this->cache_on == TRUE AND $this->cache_autodel == TRUE AND $this->_cache_init())
355                         {
356                                 $this->CACHE->delete();
357                         }
358
359                         return TRUE;
360                 }
361
362                 // Return TRUE if we don't need to create a result object
363                 // Currently only the Oracle driver uses this when stored
364                 // procedures are used
365                 if ($return_object !== TRUE)
366                 {
367                         return TRUE;
368                 }
369
370                 // Load and instantiate the result driver
371
372                 $driver                 = $this->load_rdriver();
373                 $RES                    = new $driver();
374                 $RES->conn_id   = $this->conn_id;
375                 $RES->result_id = $this->result_id;
376
377                 if ($this->dbdriver == 'oci8')
378                 {
379                         $RES->stmt_id           = $this->stmt_id;
380                         $RES->curs_id           = NULL;
381                         $RES->limit_used        = $this->limit_used;
382                         $this->stmt_id          = FALSE;
383                 }
384
385                 // oci8 vars must be set before calling this
386                 $RES->num_rows  = $RES->num_rows();
387
388                 // Is query caching enabled?  If so, we'll serialize the
389                 // result object and save it to a cache file.
390                 if ($this->cache_on == TRUE AND $this->_cache_init())
391                 {
392                         // We'll create a new instance of the result object
393                         // only without the platform specific driver since
394                         // we can't use it with cached data (the query result
395                         // resource ID won't be any good once we've cached the
396                         // result object, so we'll have to compile the data
397                         // and save it)
398                         $CR = new CI_DB_result();
399                         $CR->num_rows           = $RES->num_rows();
400                         $CR->result_object      = $RES->result_object();
401                         $CR->result_array       = $RES->result_array();
402
403                         // Reset these since cached objects can not utilize resource IDs.
404                         $CR->conn_id            = NULL;
405                         $CR->result_id          = NULL;
406
407                         $this->CACHE->write($sql, $CR);
408                 }
409
410                 return $RES;
411         }
412
413         // --------------------------------------------------------------------
414
415         /**
416          * Load the result drivers
417          *
418          * @access      public
419          * @return      string  the name of the result class
420          */
421         function load_rdriver()
422         {
423                 $driver = 'CI_DB_'.$this->dbdriver.'_result';
424
425                 if ( ! class_exists($driver))
426                 {
427                         include_once(BASEPATH.'database/DB_result'.EXT);
428                         include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result'.EXT);
429                 }
430
431                 return $driver;
432         }
433
434         // --------------------------------------------------------------------
435
436         /**
437          * Simple Query
438          * This is a simplified version of the query() function.  Internally
439          * we only use it when running transaction commands since they do
440          * not require all the features of the main query() function.
441          *
442          * @access      public
443          * @param       string  the sql query
444          * @return      mixed
445          */
446         function simple_query($sql)
447         {
448                 if ( ! $this->conn_id)
449                 {
450                         $this->initialize();
451                 }
452
453                 return $this->_execute($sql);
454         }
455
456         // --------------------------------------------------------------------
457
458         /**
459          * Disable Transactions
460          * This permits transactions to be disabled at run-time.
461          *
462          * @access      public
463          * @return      void
464          */
465         function trans_off()
466         {
467                 $this->trans_enabled = FALSE;
468         }
469
470         // --------------------------------------------------------------------
471
472         /**
473          * Enable/disable Transaction Strict Mode
474          * When strict mode is enabled, if you are running multiple groups of
475          * transactions, if one group fails all groups will be rolled back.
476          * If strict mode is disabled, each group is treated autonomously, meaning
477          * a failure of one group will not affect any others
478          *
479          * @access      public
480          * @return      void
481          */
482         function trans_strict($mode = TRUE)
483         {
484                 $this->trans_strict = is_bool($mode) ? $mode : TRUE;
485         }
486
487         // --------------------------------------------------------------------
488
489         /**
490          * Start Transaction
491          *
492          * @access      public
493          * @return      void
494          */
495         function trans_start($test_mode = FALSE)
496         {
497                 if ( ! $this->trans_enabled)
498                 {
499                         return FALSE;
500                 }
501
502                 // When transactions are nested we only begin/commit/rollback the outermost ones
503                 if ($this->_trans_depth > 0)
504                 {
505                         $this->_trans_depth += 1;
506                         return;
507                 }
508
509                 $this->trans_begin($test_mode);
510         }
511
512         // --------------------------------------------------------------------
513
514         /**
515          * Complete Transaction
516          *
517          * @access      public
518          * @return      bool
519          */
520         function trans_complete()
521         {
522                 if ( ! $this->trans_enabled)
523                 {
524                         return FALSE;
525                 }
526
527                 // When transactions are nested we only begin/commit/rollback the outermost ones
528                 if ($this->_trans_depth > 1)
529                 {
530                         $this->_trans_depth -= 1;
531                         return TRUE;
532                 }
533
534                 // The query() function will set this flag to FALSE in the event that a query failed
535                 if ($this->_trans_status === FALSE)
536                 {
537                         $this->trans_rollback();
538
539                         // If we are NOT running in strict mode, we will reset
540                         // the _trans_status flag so that subsequent groups of transactions
541                         // will be permitted.
542                         if ($this->trans_strict === FALSE)
543                         {
544                                 $this->_trans_status = TRUE;
545                         }
546
547                         log_message('debug', 'DB Transaction Failure');
548                         return FALSE;
549                 }
550
551                 $this->trans_commit();
552                 return TRUE;
553         }
554
555         // --------------------------------------------------------------------
556
557         /**
558          * Lets you retrieve the transaction flag to determine if it has failed
559          *
560          * @access      public
561          * @return      bool
562          */
563         function trans_status()
564         {
565                 return $this->_trans_status;
566         }
567
568         // --------------------------------------------------------------------
569
570         /**
571          * Compile Bindings
572          *
573          * @access      public
574          * @param       string  the sql statement
575          * @param       array   an array of bind data
576          * @return      string
577          */
578         function compile_binds($sql, $binds)
579         {
580                 if (strpos($sql, $this->bind_marker) === FALSE)
581                 {
582                         return $sql;
583                 }
584
585                 if ( ! is_array($binds))
586                 {
587                         $binds = array($binds);
588                 }
589
590                 // Get the sql segments around the bind markers
591                 $segments = explode($this->bind_marker, $sql);
592
593                 // The count of bind should be 1 less then the count of segments
594                 // If there are more bind arguments trim it down
595                 if (count($binds) >= count($segments)) {
596                         $binds = array_slice($binds, 0, count($segments)-1);
597                 }
598
599                 // Construct the binded query
600                 $result = $segments[0];
601                 $i = 0;
602                 foreach ($binds as $bind)
603                 {
604                         $result .= $this->escape($bind);
605                         $result .= $segments[++$i];
606                 }
607
608                 return $result;
609         }
610
611         // --------------------------------------------------------------------
612
613         /**
614          * Determines if a query is a "write" type.
615          *
616          * @access      public
617          * @param       string  An SQL query string
618          * @return      boolean
619          */
620         function is_write_type($sql)
621         {
622                 if ( ! preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
623                 {
624                         return FALSE;
625                 }
626                 return TRUE;
627         }
628
629         // --------------------------------------------------------------------
630
631         /**
632          * Calculate the aggregate query elapsed time
633          *
634          * @access      public
635          * @param       integer The number of decimal places
636          * @return      integer
637          */
638         function elapsed_time($decimals = 6)
639         {
640                 return number_format($this->benchmark, $decimals);
641         }
642
643         // --------------------------------------------------------------------
644
645         /**
646          * Returns the total number of queries
647          *
648          * @access      public
649          * @return      integer
650          */
651         function total_queries()
652         {
653                 return $this->query_count;
654         }
655
656         // --------------------------------------------------------------------
657
658         /**
659          * Returns the last query that was executed
660          *
661          * @access      public
662          * @return      void
663          */
664         function last_query()
665         {
666                 return end($this->queries);
667         }
668
669         // --------------------------------------------------------------------
670
671         /**
672          * "Smart" Escape String
673          *
674          * Escapes data based on type
675          * Sets boolean and null types
676          *
677          * @access      public
678          * @param       string
679          * @return      mixed
680          */
681         function escape($str)
682         {
683                 if (is_string($str))
684                 {
685                         $str = "'".$this->escape_str($str)."'";
686                 }
687                 elseif (is_bool($str))
688                 {
689                         $str = ($str === FALSE) ? 0 : 1;
690                 }
691                 elseif (is_null($str))
692                 {
693                         $str = 'NULL';
694                 }
695
696                 return $str;
697         }
698
699         // --------------------------------------------------------------------
700
701         /**
702          * Escape LIKE String
703          *
704          * Calls the individual driver for platform
705          * specific escaping for LIKE conditions
706          *
707          * @access      public
708          * @param       string
709          * @return      mixed
710          */
711         function escape_like_str($str)
712         {
713                 return $this->escape_str($str, TRUE);
714         }
715
716         // --------------------------------------------------------------------
717
718         /**
719          * Primary
720          *
721          * Retrieves the primary key.  It assumes that the row in the first
722          * position is the primary key
723          *
724          * @access      public
725          * @param       string  the table name
726          * @return      string
727          */
728         function primary($table = '')
729         {
730                 $fields = $this->list_fields($table);
731
732                 if ( ! is_array($fields))
733                 {
734                         return FALSE;
735                 }
736
737                 return current($fields);
738         }
739
740         // --------------------------------------------------------------------
741
742         /**
743          * Returns an array of table names
744          *
745          * @access      public
746          * @return      array
747          */
748         function list_tables($constrain_by_prefix = FALSE)
749         {
750                 // Is there a cached result?
751                 if (isset($this->data_cache['table_names']))
752                 {
753                         return $this->data_cache['table_names'];
754                 }
755
756                 if (FALSE === ($sql = $this->_list_tables($constrain_by_prefix)))
757                 {
758                         if ($this->db_debug)
759                         {
760                                 return $this->display_error('db_unsupported_function');
761                         }
762                         return FALSE;
763                 }
764
765                 $retval = array();
766                 $query = $this->query($sql);
767
768                 if ($query->num_rows() > 0)
769                 {
770                         foreach ($query->result_array() as $row)
771                         {
772                                 if (isset($row['TABLE_NAME']))
773                                 {
774                                         $retval[] = $row['TABLE_NAME'];
775                                 }
776                                 else
777                                 {
778                                         $retval[] = array_shift($row);
779                                 }
780                         }
781                 }
782
783                 $this->data_cache['table_names'] = $retval;
784                 return $this->data_cache['table_names'];
785         }
786
787         // --------------------------------------------------------------------
788
789         /**
790          * Determine if a particular table exists
791          * @access      public
792          * @return      boolean
793          */
794         function table_exists($table_name)
795         {
796                 return ( ! in_array($this->_protect_identifiers($table_name, TRUE, FALSE, FALSE), $this->list_tables())) ? FALSE : TRUE;
797         }
798
799         // --------------------------------------------------------------------
800
801         /**
802          * Fetch MySQL Field Names
803          *
804          * @access      public
805          * @param       string  the table name
806          * @return      array
807          */
808         function list_fields($table = '')
809         {
810                 // Is there a cached result?
811                 if (isset($this->data_cache['field_names'][$table]))
812                 {
813                         return $this->data_cache['field_names'][$table];
814                 }
815
816                 if ($table == '')
817                 {
818                         if ($this->db_debug)
819                         {
820                                 return $this->display_error('db_field_param_missing');
821                         }
822                         return FALSE;
823                 }
824
825                 if (FALSE === ($sql = $this->_list_columns($table)))
826                 {
827                         if ($this->db_debug)
828                         {
829                                 return $this->display_error('db_unsupported_function');
830                         }
831                         return FALSE;
832                 }
833
834                 $query = $this->query($sql);
835
836                 $retval = array();
837                 foreach ($query->result_array() as $row)
838                 {
839                         if (isset($row['COLUMN_NAME']))
840                         {
841                                 $retval[] = $row['COLUMN_NAME'];
842                         }
843                         else
844                         {
845                                 $retval[] = current($row);
846                         }
847                 }
848
849                 $this->data_cache['field_names'][$table] = $retval;
850                 return $this->data_cache['field_names'][$table];
851         }
852
853         // --------------------------------------------------------------------
854
855         /**
856          * Determine if a particular field exists
857          * @access      public
858          * @param       string
859          * @param       string
860          * @return      boolean
861          */
862         function field_exists($field_name, $table_name)
863         {
864                 return ( ! in_array($field_name, $this->list_fields($table_name))) ? FALSE : TRUE;
865         }
866
867         // --------------------------------------------------------------------
868
869         /**
870          * Returns an object with field data
871          *
872          * @access      public
873          * @param       string  the table name
874          * @return      object
875          */
876         function field_data($table = '')
877         {
878                 if ($table == '')
879                 {
880                         if ($this->db_debug)
881                         {
882                                 return $this->display_error('db_field_param_missing');
883                         }
884                         return FALSE;
885                 }
886
887                 $query = $this->query($this->_field_data($this->_protect_identifiers($table, TRUE, NULL, FALSE)));
888
889                 return $query->field_data();
890         }
891
892         // --------------------------------------------------------------------
893
894         /**
895          * Generate an insert string
896          *
897          * @access      public
898          * @param       string  the table upon which the query will be performed
899          * @param       array   an associative array data of key/values
900          * @return      string
901          */
902         function insert_string($table, $data)
903         {
904                 $fields = array();
905                 $values = array();
906
907                 foreach ($data as $key => $val)
908                 {
909                         $fields[] = $this->_escape_identifiers($key);
910                         $values[] = $this->escape($val);
911                 }
912
913                 return $this->_insert($this->_protect_identifiers($table, TRUE, NULL, FALSE), $fields, $values);
914         }
915
916         // --------------------------------------------------------------------
917
918         /**
919          * Generate an update string
920          *
921          * @access      public
922          * @param       string  the table upon which the query will be performed
923          * @param       array   an associative array data of key/values
924          * @param       mixed   the "where" statement
925          * @return      string
926          */
927         function update_string($table, $data, $where)
928         {
929                 if ($where == '')
930                 {
931                         return false;
932                 }
933
934                 $fields = array();
935                 foreach ($data as $key => $val)
936                 {
937                         $fields[$this->_protect_identifiers($key)] = $this->escape($val);
938                 }
939
940                 if ( ! is_array($where))
941                 {
942                         $dest = array($where);
943                 }
944                 else
945                 {
946                         $dest = array();
947                         foreach ($where as $key => $val)
948                         {
949                                 $prefix = (count($dest) == 0) ? '' : ' AND ';
950
951                                 if ($val !== '')
952                                 {
953                                         if ( ! $this->_has_operator($key))
954                                         {
955                                                 $key .= ' =';
956                                         }
957
958                                         $val = ' '.$this->escape($val);
959                                 }
960
961                                 $dest[] = $prefix.$key.$val;
962                         }
963                 }
964
965                 return $this->_update($this->_protect_identifiers($table, TRUE, NULL, FALSE), $fields, $dest);
966         }
967
968         // --------------------------------------------------------------------
969
970         /**
971          * Tests whether the string has an SQL operator
972          *
973          * @access      private
974          * @param       string
975          * @return      bool
976          */
977         function _has_operator($str)
978         {
979                 $str = trim($str);
980                 if ( ! preg_match("/(\s|<|>|!|=|is null|is not null)/i", $str))
981                 {
982                         return FALSE;
983                 }
984
985                 return TRUE;
986         }
987
988         // --------------------------------------------------------------------
989
990         /**
991          * Enables a native PHP function to be run, using a platform agnostic wrapper.
992          *
993          * @access      public
994          * @param       string  the function name
995          * @param       mixed   any parameters needed by the function
996          * @return      mixed
997          */
998         function call_function($function)
999         {
1000                 $driver = ($this->dbdriver == 'postgre') ? 'pg_' : $this->dbdriver.'_';
1001
1002                 if (FALSE === strpos($driver, $function))
1003                 {
1004                         $function = $driver.$function;
1005                 }
1006
1007                 if ( ! function_exists($function))
1008                 {
1009                         if ($this->db_debug)
1010                         {
1011                                 return $this->display_error('db_unsupported_function');
1012                         }
1013                         return FALSE;
1014                 }
1015                 else
1016                 {
1017                         $args = (func_num_args() > 1) ? array_splice(func_get_args(), 1) : null;
1018
1019                         return call_user_func_array($function, $args);
1020                 }
1021         }
1022
1023         // --------------------------------------------------------------------
1024
1025         /**
1026          * Set Cache Directory Path
1027          *
1028          * @access      public
1029          * @param       string  the path to the cache directory
1030          * @return      void
1031          */
1032         function cache_set_path($path = '')
1033         {
1034                 $this->cachedir = $path;
1035         }
1036
1037         // --------------------------------------------------------------------
1038
1039         /**
1040          * Enable Query Caching
1041          *
1042          * @access      public
1043          * @return      void
1044          */
1045         function cache_on()
1046         {
1047                 $this->cache_on = TRUE;
1048                 return TRUE;
1049         }
1050
1051         // --------------------------------------------------------------------
1052
1053         /**
1054          * Disable Query Caching
1055          *
1056          * @access      public
1057          * @return      void
1058          */
1059         function cache_off()
1060         {
1061                 $this->cache_on = FALSE;
1062                 return FALSE;
1063         }
1064
1065
1066         // --------------------------------------------------------------------
1067
1068         /**
1069          * Delete the cache files associated with a particular URI
1070          *
1071          * @access      public
1072          * @return      void
1073          */
1074         function cache_delete($segment_one = '', $segment_two = '')
1075         {
1076                 if ( ! $this->_cache_init())
1077                 {
1078                         return FALSE;
1079                 }
1080                 return $this->CACHE->delete($segment_one, $segment_two);
1081         }
1082
1083         // --------------------------------------------------------------------
1084
1085         /**
1086          * Delete All cache files
1087          *
1088          * @access      public
1089          * @return      void
1090          */
1091         function cache_delete_all()
1092         {
1093                 if ( ! $this->_cache_init())
1094                 {
1095                         return FALSE;
1096                 }
1097
1098                 return $this->CACHE->delete_all();
1099         }
1100
1101         // --------------------------------------------------------------------
1102
1103         /**
1104          * Initialize the Cache Class
1105          *
1106          * @access      private
1107          * @return      void
1108          */
1109         function _cache_init()
1110         {
1111                 if (is_object($this->CACHE) AND class_exists('CI_DB_Cache'))
1112                 {
1113                         return TRUE;
1114                 }
1115
1116                 if ( ! class_exists('CI_DB_Cache'))
1117                 {
1118                         if ( ! @include(BASEPATH.'database/DB_cache'.EXT))
1119                         {
1120                                 return $this->cache_off();
1121                         }
1122                 }
1123
1124                 $this->CACHE = new CI_DB_Cache($this); // pass db object to support multiple db connections and returned db objects
1125                 return TRUE;
1126         }
1127
1128         // --------------------------------------------------------------------
1129
1130         /**
1131          * Close DB Connection
1132          *
1133          * @access      public
1134          * @return      void
1135          */
1136         function close()
1137         {
1138                 if (is_resource($this->conn_id) OR is_object($this->conn_id))
1139                 {
1140                         $this->_close($this->conn_id);
1141                 }
1142                 $this->conn_id = FALSE;
1143         }
1144
1145         // --------------------------------------------------------------------
1146
1147         /**
1148          * Display an error message
1149          *
1150          * @access      public
1151          * @param       string  the error message
1152          * @param       string  any "swap" values
1153          * @param       boolean whether to localize the message
1154          * @return      string  sends the application/error_db.php template
1155          */
1156         function display_error($error = '', $swap = '', $native = FALSE)
1157         {
1158                 $LANG =& load_class('Lang', 'core');
1159                 $LANG->load('db');
1160
1161                 $heading = $LANG->line('db_error_heading');
1162
1163                 if ($native == TRUE)
1164                 {
1165                         $message = $error;
1166                 }
1167                 else
1168                 {
1169                         $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
1170                 }
1171
1172                 // Find the most likely culprit of the error by going through
1173                 // the backtrace until the source file is no longer in the
1174                 // database folder.
1175
1176                 $trace = debug_backtrace();
1177
1178                 foreach ($trace as $call)
1179                 {
1180                         if (isset($call['file']) && strpos($call['file'], BASEPATH.'database') === FALSE)
1181                         {
1182                                 // Found it - use a relative path for safety
1183                                 $message[] = 'Filename: '.str_replace(array(BASEPATH, APPPATH), '', $call['file']);
1184                                 $message[] = 'Line Number: '.$call['line'];
1185
1186                                 break;
1187                         }
1188                 }
1189
1190                 $error =& load_class('Exceptions', 'core');
1191                 echo $error->show_error($heading, $message, 'error_db');
1192                 exit;
1193         }
1194
1195         // --------------------------------------------------------------------
1196
1197         /**
1198          * Protect Identifiers
1199          *
1200          * This function adds backticks if appropriate based on db type
1201          *
1202          * @access      private
1203          * @param       mixed   the item to escape
1204          * @return      mixed   the item with backticks
1205          */
1206         function protect_identifiers($item, $prefix_single = FALSE)
1207         {
1208                 return $this->_protect_identifiers($item, $prefix_single);
1209         }
1210
1211         // --------------------------------------------------------------------
1212
1213         /**
1214          * Protect Identifiers
1215          *
1216          * This function is used extensively by the Active Record class, and by
1217          * a couple functions in this class.
1218          * It takes a column or table name (optionally with an alias) and inserts
1219          * the table prefix onto it.  Some logic is necessary in order to deal with
1220          * column names that include the path.  Consider a query like this:
1221          *
1222          * SELECT * FROM hostname.database.table.column AS c FROM hostname.database.table
1223          *
1224          * Or a query with aliasing:
1225          *
1226          * SELECT m.member_id, m.member_name FROM members AS m
1227          *
1228          * Since the column name can include up to four segments (host, DB, table, column)
1229          * or also have an alias prefix, we need to do a bit of work to figure this out and
1230          * insert the table prefix (if it exists) in the proper position, and escape only
1231          * the correct identifiers.
1232          *
1233          * @access      private
1234          * @param       string
1235          * @param       bool
1236          * @param       mixed
1237          * @param       bool
1238          * @return      string
1239          */
1240         function _protect_identifiers($item, $prefix_single = FALSE, $protect_identifiers = NULL, $field_exists = TRUE)
1241         {
1242                 if ( ! is_bool($protect_identifiers))
1243                 {
1244                         $protect_identifiers = $this->_protect_identifiers;
1245                 }
1246
1247                 if (is_array($item))
1248                 {
1249                         $escaped_array = array();
1250
1251                         foreach ($item as $k => $v)
1252                         {
1253                                 $escaped_array[$this->_protect_identifiers($k)] = $this->_protect_identifiers($v);
1254                         }
1255
1256                         return $escaped_array;
1257                 }
1258
1259                 // Convert tabs or multiple spaces into single spaces
1260                 $item = preg_replace('/[\t ]+/', ' ', $item);
1261
1262                 // If the item has an alias declaration we remove it and set it aside.
1263                 // Basically we remove everything to the right of the first space
1264                 $alias = '';
1265                 if (strpos($item, ' ') !== FALSE)
1266                 {
1267                         $alias = strstr($item, " ");
1268                         $item = substr($item, 0, - strlen($alias));
1269                 }
1270
1271                 // This is basically a bug fix for queries that use MAX, MIN, etc.
1272                 // If a parenthesis is found we know that we do not need to
1273                 // escape the data or add a prefix.  There's probably a more graceful
1274                 // way to deal with this, but I'm not thinking of it -- Rick
1275                 if (strpos($item, '(') !== FALSE)
1276                 {
1277                         return $item.$alias;
1278                 }
1279
1280                 // Break the string apart if it contains periods, then insert the table prefix
1281                 // in the correct location, assuming the period doesn't indicate that we're dealing
1282                 // with an alias. While we're at it, we will escape the components
1283                 if (strpos($item, '.') !== FALSE)
1284                 {
1285                         $parts  = explode('.', $item);
1286
1287                         // Does the first segment of the exploded item match
1288                         // one of the aliases previously identified?  If so,
1289                         // we have nothing more to do other than escape the item
1290                         if (in_array($parts[0], $this->ar_aliased_tables))
1291                         {
1292                                 if ($protect_identifiers === TRUE)
1293                                 {
1294                                         foreach ($parts as $key => $val)
1295                                         {
1296                                                 if ( ! in_array($val, $this->_reserved_identifiers))
1297                                                 {
1298                                                         $parts[$key] = $this->_escape_identifiers($val);
1299                                                 }
1300                                         }
1301
1302                                         $item = implode('.', $parts);
1303                                 }
1304                                 return $item.$alias;
1305                         }
1306
1307                         // Is there a table prefix defined in the config file?  If not, no need to do anything
1308                         if ($this->dbprefix != '')
1309                         {
1310                                 // We now add the table prefix based on some logic.
1311                                 // Do we have 4 segments (hostname.database.table.column)?
1312                                 // If so, we add the table prefix to the column name in the 3rd segment.
1313                                 if (isset($parts[3]))
1314                                 {
1315                                         $i = 2;
1316                                 }
1317                                 // Do we have 3 segments (database.table.column)?
1318                                 // If so, we add the table prefix to the column name in 2nd position
1319                                 elseif (isset($parts[2]))
1320                                 {
1321                                         $i = 1;
1322                                 }
1323                                 // Do we have 2 segments (table.column)?
1324                                 // If so, we add the table prefix to the column name in 1st segment
1325                                 else
1326                                 {
1327                                         $i = 0;
1328                                 }
1329
1330                                 // This flag is set when the supplied $item does not contain a field name.
1331                                 // This can happen when this function is being called from a JOIN.
1332                                 if ($field_exists == FALSE)
1333                                 {
1334                                         $i++;
1335                                 }
1336
1337                                 // Verify table prefix and replace if necessary
1338                                 if ($this->swap_pre != '' && strncmp($parts[$i], $this->swap_pre, strlen($this->swap_pre)) === 0)
1339                                 {
1340                                         $parts[$i] = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $parts[$i]);
1341                                 }
1342
1343                                 // We only add the table prefix if it does not already exist
1344                                 if (substr($parts[$i], 0, strlen($this->dbprefix)) != $this->dbprefix)
1345                                 {
1346                                         $parts[$i] = $this->dbprefix.$parts[$i];
1347                                 }
1348
1349                                 // Put the parts back together
1350                                 $item = implode('.', $parts);
1351                         }
1352
1353                         if ($protect_identifiers === TRUE)
1354                         {
1355                                 $item = $this->_escape_identifiers($item);
1356                         }
1357
1358                         return $item.$alias;
1359                 }
1360
1361                 // Is there a table prefix?  If not, no need to insert it
1362                 if ($this->dbprefix != '')
1363                 {
1364                         // Verify table prefix and replace if necessary
1365                         if ($this->swap_pre != '' && strncmp($item, $this->swap_pre, strlen($this->swap_pre)) === 0)
1366                         {
1367                                 $item = preg_replace("/^".$this->swap_pre."(\S+?)/", $this->dbprefix."\\1", $item);
1368                         }
1369
1370                         // Do we prefix an item with no segments?
1371                         if ($prefix_single == TRUE AND substr($item, 0, strlen($this->dbprefix)) != $this->dbprefix)
1372                         {
1373                                 $item = $this->dbprefix.$item;
1374                         }
1375                 }
1376
1377                 if ($protect_identifiers === TRUE AND ! in_array($item, $this->_reserved_identifiers))
1378                 {
1379                         $item = $this->_escape_identifiers($item);
1380                 }
1381
1382                 return $item.$alias;
1383         }
1384
1385
1386 }
1387
1388
1389 /* End of file DB_driver.php */
1390 /* Location: ./system/database/DB_driver.php */