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