Initial Kohana install
[speedfreak] / Server / system / libraries / Database.php
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
2 /**
3  * Provides database access in a platform agnostic way, using simple query building blocks.
4  *
5  * $Id: Database.php 4342 2009-05-08 16:56:01Z jheathco $
6  *
7  * @package    Core
8  * @author     Kohana Team
9  * @copyright  (c) 2007-2008 Kohana Team
10  * @license    http://kohanaphp.com/license.html
11  */
12 class Database_Core {
13
14         // Database instances
15         public static $instances = array();
16
17         // Global benchmark
18         public static $benchmarks = array();
19
20         // Configuration
21         protected $config = array
22         (
23                 'benchmark'     => TRUE,
24                 'persistent'    => FALSE,
25                 'connection'    => '',
26                 'character_set' => 'utf8',
27                 'table_prefix'  => '',
28                 'object'        => TRUE,
29                 'cache'         => FALSE,
30                 'escape'        => TRUE,
31         );
32
33         // Database driver object
34         protected $driver;
35         protected $link;
36
37         // Un-compiled parts of the SQL query
38         protected $select     = array();
39         protected $set        = array();
40         protected $from       = array();
41         protected $join       = array();
42         protected $where      = array();
43         protected $orderby    = array();
44         protected $order      = array();
45         protected $groupby    = array();
46         protected $having     = array();
47         protected $distinct   = FALSE;
48         protected $limit      = FALSE;
49         protected $offset     = FALSE;
50         protected $last_query = '';
51
52         // Stack of queries for push/pop
53         protected $query_history = array();
54
55         /**
56          * Returns a singleton instance of Database.
57          *
58          * @param   mixed   configuration array or DSN
59          * @return  Database_Core
60          */
61         public static function & instance($name = 'default', $config = NULL)
62         {
63                 if ( ! isset(Database::$instances[$name]))
64                 {
65                         // Create a new instance
66                         Database::$instances[$name] = new Database($config === NULL ? $name : $config);
67                 }
68
69                 return Database::$instances[$name];
70         }
71
72         /**
73          * Returns the name of a given database instance.
74          *
75          * @param   Database  instance of Database
76          * @return  string
77          */
78         public static function instance_name(Database $db)
79         {
80                 return array_search($db, Database::$instances, TRUE);
81         }
82
83         /**
84          * Sets up the database configuration, loads the Database_Driver.
85          *
86          * @throws  Kohana_Database_Exception
87          */
88         public function __construct($config = array())
89         {
90                 if (empty($config))
91                 {
92                         // Load the default group
93                         $config = Kohana::config('database.default');
94                 }
95                 elseif (is_array($config) AND count($config) > 0)
96                 {
97                         if ( ! array_key_exists('connection', $config))
98                         {
99                                 $config = array('connection' => $config);
100                         }
101                 }
102                 elseif (is_string($config))
103                 {
104                         // The config is a DSN string
105                         if (strpos($config, '://') !== FALSE)
106                         {
107                                 $config = array('connection' => $config);
108                         }
109                         // The config is a group name
110                         else
111                         {
112                                 $name = $config;
113
114                                 // Test the config group name
115                                 if (($config = Kohana::config('database.'.$config)) === NULL)
116                                         throw new Kohana_Database_Exception('database.undefined_group', $name);
117                         }
118                 }
119
120                 // Merge the default config with the passed config
121                 $this->config = array_merge($this->config, $config);
122
123                 if (is_string($this->config['connection']))
124                 {
125                         // Make sure the connection is valid
126                         if (strpos($this->config['connection'], '://') === FALSE)
127                                 throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
128
129                         // Parse the DSN, creating an array to hold the connection parameters
130                         $db = array
131                         (
132                                 'type'     => FALSE,
133                                 'user'     => FALSE,
134                                 'pass'     => FALSE,
135                                 'host'     => FALSE,
136                                 'port'     => FALSE,
137                                 'socket'   => FALSE,
138                                 'database' => FALSE
139                         );
140
141                         // Get the protocol and arguments
142                         list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
143
144                         if (strpos($connection, '@') !== FALSE)
145                         {
146                                 // Get the username and password
147                                 list ($db['pass'], $connection) = explode('@', $connection, 2);
148                                 // Check if a password is supplied
149                                 $logindata = explode(':', $db['pass'], 2);
150                                 $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
151                                 $db['user'] = $logindata[0];
152
153                                 // Prepare for finding the database
154                                 $connection = explode('/', $connection);
155
156                                 // Find the database name
157                                 $db['database'] = array_pop($connection);
158
159                                 // Reset connection string
160                                 $connection = implode('/', $connection);
161
162                                 // Find the socket
163                                 if (preg_match('/^unix\([^)]++\)/', $connection))
164                                 {
165                                         // This one is a little hairy: we explode based on the end of
166                                         // the socket, removing the 'unix(' from the connection string
167                                         list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
168                                 }
169                                 elseif (strpos($connection, ':') !== FALSE)
170                                 {
171                                         // Fetch the host and port name
172                                         list ($db['host'], $db['port']) = explode(':', $connection, 2);
173                                 }
174                                 else
175                                 {
176                                         $db['host'] = $connection;
177                                 }
178                         }
179                         else
180                         {
181                                 // File connection
182                                 $connection = explode('/', $connection);
183
184                                 // Find database file name
185                                 $db['database'] = array_pop($connection);
186
187                                 // Find database directory name
188                                 $db['socket'] = implode('/', $connection).'/';
189                         }
190
191                         // Reset the connection array to the database config
192                         $this->config['connection'] = $db;
193                 }
194                 // Set driver name
195                 $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
196
197                 // Load the driver
198                 if ( ! Kohana::auto_load($driver))
199                         throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
200
201                 // Initialize the driver
202                 $this->driver = new $driver($this->config);
203
204                 // Validate the driver
205                 if ( ! ($this->driver instanceof Database_Driver))
206                         throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
207
208                 Kohana::log('debug', 'Database Library initialized');
209         }
210
211         /**
212          * Simple connect method to get the database queries up and running.
213          *
214          * @return  void
215          */
216         public function connect()
217         {
218                 // A link can be a resource or an object
219                 if ( ! is_resource($this->link) AND ! is_object($this->link))
220                 {
221                         $this->link = $this->driver->connect();
222                         if ( ! is_resource($this->link) AND ! is_object($this->link))
223                                 throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
224
225                         // Clear password after successful connect
226                         $this->config['connection']['pass'] = NULL;
227                 }
228         }
229
230         /**
231          * Runs a query into the driver and returns the result.
232          *
233          * @param   string  SQL query to execute
234          * @return  Database_Result
235          */
236         public function query($sql = '')
237         {
238                 if ($sql == '') return FALSE;
239
240                 // No link? Connect!
241                 $this->link or $this->connect();
242
243                 // Start the benchmark
244                 $start = microtime(TRUE);
245
246                 if (func_num_args() > 1) //if we have more than one argument ($sql)
247                 {
248                         $argv = func_get_args();
249                         $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
250                 }
251
252                 // Compile binds if needed
253                 if (isset($binds))
254                 {
255                         $sql = $this->compile_binds($sql, $binds);
256                 }
257
258                 // Fetch the result
259                 $result = $this->driver->query($this->last_query = $sql);
260
261                 // Stop the benchmark
262                 $stop = microtime(TRUE);
263
264                 if ($this->config['benchmark'] == TRUE)
265                 {
266                         // Benchmark the query
267                         Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
268                 }
269
270                 return $result;
271         }
272
273         /**
274          * Selects the column names for a database query.
275          *
276          * @param   string  string or array of column names to select
277          * @return  Database_Core  This Database object.
278          */
279         public function select($sql = '*')
280         {
281                 if (func_num_args() > 1)
282                 {
283                         $sql = func_get_args();
284                 }
285                 elseif (is_string($sql))
286                 {
287                         $sql = explode(',', $sql);
288                 }
289                 else
290                 {
291                         $sql = (array) $sql;
292                 }
293
294                 foreach ($sql as $val)
295                 {
296                         if (($val = trim($val)) === '') continue;
297
298                         if (strpos($val, '(') === FALSE AND $val !== '*')
299                         {
300                                 if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
301                                 {
302                                         // Only prepend with table prefix if table name is specified
303                                         $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1];
304
305                                         $this->distinct = TRUE;
306                                 }
307                                 else
308                                 {
309                                         $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
310                                 }
311
312                                 $val = $this->driver->escape_column($val);
313                         }
314
315                         $this->select[] = $val;
316                 }
317
318                 return $this;
319         }
320
321         /**
322          * Selects the from table(s) for a database query.
323          *
324          * @param   string  string or array of tables to select
325          * @return  Database_Core  This Database object.
326          */
327         public function from($sql)
328         {
329                 if (func_num_args() > 1)
330                 {
331                         $sql = func_get_args();
332                 }
333                 elseif (is_string($sql))
334                 {
335                         $sql = explode(',', $sql);
336                 }
337                 else
338                 {
339                         $sql = array($sql);
340                 }
341
342                 foreach ($sql as $val)
343                 {
344                         if (is_string($val))
345                         {
346                                 if (($val = trim($val)) === '') continue;
347
348                                 // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
349                                 if (stripos($val, ' AS ') !== FALSE)
350                                 {
351                                         $val = str_ireplace(' AS ', ' AS ', $val);
352
353                                         list($table, $alias) = explode(' AS ', $val);
354
355                                         // Attach prefix to both sides of the AS
356                                         $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
357                                 }
358                                 else
359                                 {
360                                         $val = $this->config['table_prefix'].$val;
361                                 }
362                         }
363
364                         $this->from[] = $val;
365                 }
366
367                 return $this;
368         }
369
370         /**
371          * Generates the JOIN portion of the query.
372          *
373          * @param   string        table name
374          * @param   string|array  where key or array of key => value pairs
375          * @param   string        where value
376          * @param   string        type of join
377          * @return  Database_Core        This Database object.
378          */
379         public function join($table, $key, $value = NULL, $type = '')
380         {
381                 $join = array();
382
383                 if ( ! empty($type))
384                 {
385                         $type = strtoupper(trim($type));
386
387                         if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
388                         {
389                                 $type = '';
390                         }
391                         else
392                         {
393                                 $type .= ' ';
394                         }
395                 }
396
397                 $cond = array();
398                 $keys  = is_array($key) ? $key : array($key => $value);
399                 foreach ($keys as $key => $value)
400                 {
401                         $key    = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
402
403                         if (is_string($value))
404                         {
405                                 // Only escape if it's a string
406                                 $value = $this->driver->escape_column($this->config['table_prefix'].$value);
407                         }
408
409                         $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE);
410                 }
411
412                 if ( ! is_array($this->join))
413                 {
414                         $this->join = array();
415                 }
416
417                 if ( ! is_array($table))
418                 {
419                         $table = array($table);
420                 }
421
422                 foreach ($table as $t)
423                 {
424                         if (is_string($t))
425                         {
426                                 // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
427                                 if (stripos($t, ' AS ') !== FALSE)
428                                 {
429                                         $t = str_ireplace(' AS ', ' AS ', $t);
430
431                                         list($table, $alias) = explode(' AS ', $t);
432
433                                         // Attach prefix to both sides of the AS
434                                         $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
435                                 }
436                                 else
437                                 {
438                                         $t = $this->config['table_prefix'].$t;
439                                 }
440                         }
441
442                         $join['tables'][] = $this->driver->escape_column($t);
443                 }
444
445                 $join['conditions'] = '('.trim(implode(' ', $cond)).')';
446                 $join['type'] = $type;
447
448                 $this->join[] = $join;
449
450                 return $this;
451         }
452
453
454         /**
455          * Selects the where(s) for a database query.
456          *
457          * @param   string|array  key name or array of key => value pairs
458          * @param   string        value to match with key
459          * @param   boolean       disable quoting of WHERE clause
460          * @return  Database_Core        This Database object.
461          */
462         public function where($key, $value = NULL, $quote = TRUE)
463         {
464                 $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
465                 if (is_object($key))
466                 {
467                         $keys = array((string) $key => '');
468                 }
469                 elseif ( ! is_array($key))
470                 {
471                         $keys = array($key => $value);
472                 }
473                 else
474                 {
475                         $keys = $key;
476                 }
477
478                 foreach ($keys as $key => $value)
479                 {
480                         $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
481                         $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
482                 }
483
484                 return $this;
485         }
486
487         /**
488          * Selects the or where(s) for a database query.
489          *
490          * @param   string|array  key name or array of key => value pairs
491          * @param   string        value to match with key
492          * @param   boolean       disable quoting of WHERE clause
493          * @return  Database_Core        This Database object.
494          */
495         public function orwhere($key, $value = NULL, $quote = TRUE)
496         {
497                 $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
498                 if (is_object($key))
499                 {
500                         $keys = array((string) $key => '');
501                 }
502                 elseif ( ! is_array($key))
503                 {
504                         $keys = array($key => $value);
505                 }
506                 else
507                 {
508                         $keys = $key;
509                 }
510
511                 foreach ($keys as $key => $value)
512                 {
513                         $key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
514                         $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
515                 }
516
517                 return $this;
518         }
519
520         /**
521          * Selects the like(s) for a database query.
522          *
523          * @param   string|array  field name or array of field => match pairs
524          * @param   string        like value to match with field
525          * @param   boolean       automatically add starting and ending wildcards
526          * @return  Database_Core        This Database object.
527          */
528         public function like($field, $match = '', $auto = TRUE)
529         {
530                 $fields = is_array($field) ? $field : array($field => $match);
531
532                 foreach ($fields as $field => $match)
533                 {
534                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
535                         $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
536                 }
537
538                 return $this;
539         }
540
541         /**
542          * Selects the or like(s) for a database query.
543          *
544          * @param   string|array  field name or array of field => match pairs
545          * @param   string        like value to match with field
546          * @param   boolean       automatically add starting and ending wildcards
547          * @return  Database_Core        This Database object.
548          */
549         public function orlike($field, $match = '', $auto = TRUE)
550         {
551                 $fields = is_array($field) ? $field : array($field => $match);
552
553                 foreach ($fields as $field => $match)
554                 {
555                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
556                         $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
557                 }
558
559                 return $this;
560         }
561
562         /**
563          * Selects the not like(s) for a database query.
564          *
565          * @param   string|array  field name or array of field => match pairs
566          * @param   string        like value to match with field
567          * @param   boolean       automatically add starting and ending wildcards
568          * @return  Database_Core        This Database object.
569          */
570         public function notlike($field, $match = '', $auto = TRUE)
571         {
572                 $fields = is_array($field) ? $field : array($field => $match);
573
574                 foreach ($fields as $field => $match)
575                 {
576                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
577                         $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
578                 }
579
580                 return $this;
581         }
582
583         /**
584          * Selects the or not like(s) for a database query.
585          *
586          * @param   string|array  field name or array of field => match pairs
587          * @param   string        like value to match with field
588          * @return  Database_Core        This Database object.
589          */
590         public function ornotlike($field, $match = '', $auto = TRUE)
591         {
592                 $fields = is_array($field) ? $field : array($field => $match);
593
594                 foreach ($fields as $field => $match)
595                 {
596                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
597                         $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
598                 }
599
600                 return $this;
601         }
602
603         /**
604          * Selects the like(s) for a database query.
605          *
606          * @param   string|array  field name or array of field => match pairs
607          * @param   string        like value to match with field
608          * @return  Database_Core        This Database object.
609          */
610         public function regex($field, $match = '')
611         {
612                 $fields = is_array($field) ? $field : array($field => $match);
613
614                 foreach ($fields as $field => $match)
615                 {
616                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
617                         $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
618                 }
619
620                 return $this;
621         }
622
623         /**
624          * Selects the or like(s) for a database query.
625          *
626          * @param   string|array  field name or array of field => match pairs
627          * @param   string        like value to match with field
628          * @return  Database_Core        This Database object.
629          */
630         public function orregex($field, $match = '')
631         {
632                 $fields = is_array($field) ? $field : array($field => $match);
633
634                 foreach ($fields as $field => $match)
635                 {
636                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
637                         $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
638                 }
639
640                 return $this;
641         }
642
643         /**
644          * Selects the not regex(s) for a database query.
645          *
646          * @param   string|array  field name or array of field => match pairs
647          * @param   string        regex value to match with field
648          * @return  Database_Core        This Database object.
649          */
650         public function notregex($field, $match = '')
651         {
652                 $fields = is_array($field) ? $field : array($field => $match);
653
654                 foreach ($fields as $field => $match)
655                 {
656                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
657                         $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
658                 }
659
660                 return $this;
661         }
662
663         /**
664          * Selects the or not regex(s) for a database query.
665          *
666          * @param   string|array  field name or array of field => match pairs
667          * @param   string        regex value to match with field
668          * @return  Database_Core        This Database object.
669          */
670         public function ornotregex($field, $match = '')
671         {
672                 $fields = is_array($field) ? $field : array($field => $match);
673
674                 foreach ($fields as $field => $match)
675                 {
676                         $field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
677                         $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
678                 }
679
680                 return $this;
681         }
682
683         /**
684          * Chooses the column to group by in a select query.
685          *
686          * @param   string  column name to group by
687          * @return  Database_Core  This Database object.
688          */
689         public function groupby($by)
690         {
691                 if ( ! is_array($by))
692                 {
693                         $by = explode(',', (string) $by);
694                 }
695
696                 foreach ($by as $val)
697                 {
698                         $val = trim($val);
699
700                         if ($val != '')
701                         {
702                                 // Add the table prefix if we are using table.column names
703                                 if(strpos($val, '.'))
704                                 {
705                                         $val = $this->config['table_prefix'].$val;
706                                 }
707
708                                 $this->groupby[] = $this->driver->escape_column($val);
709                         }
710                 }
711
712                 return $this;
713         }
714
715         /**
716          * Selects the having(s) for a database query.
717          *
718          * @param   string|array  key name or array of key => value pairs
719          * @param   string        value to match with key
720          * @param   boolean       disable quoting of WHERE clause
721          * @return  Database_Core        This Database object.
722          */
723         public function having($key, $value = '', $quote = TRUE)
724         {
725                 $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
726                 return $this;
727         }
728
729         /**
730          * Selects the or having(s) for a database query.
731          *
732          * @param   string|array  key name or array of key => value pairs
733          * @param   string        value to match with key
734          * @param   boolean       disable quoting of WHERE clause
735          * @return  Database_Core        This Database object.
736          */
737         public function orhaving($key, $value = '', $quote = TRUE)
738         {
739                 $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
740                 return $this;
741         }
742
743         /**
744          * Chooses which column(s) to order the select query by.
745          *
746          * @param   string|array  column(s) to order on, can be an array, single column, or comma seperated list of columns
747          * @param   string        direction of the order
748          * @return  Database_Core        This Database object.
749          */
750         public function orderby($orderby, $direction = NULL)
751         {
752                 if ( ! is_array($orderby))
753                 {
754                         $orderby = array($orderby => $direction);
755                 }
756
757                 foreach ($orderby as $column => $direction)
758                 {
759                         $direction = strtoupper(trim($direction));
760
761                         // Add a direction if the provided one isn't valid
762                         if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
763                         {
764                                 $direction = 'ASC';
765                         }
766
767                         // Add the table prefix if a table.column was passed
768                         if (strpos($column, '.'))
769                         {
770                                 $column = $this->config['table_prefix'].$column;
771                         }
772
773                         $this->orderby[] = $this->driver->escape_column($column).' '.$direction;
774                 }
775
776                 return $this;
777         }
778
779         /**
780          * Selects the limit section of a query.
781          *
782          * @param   integer  number of rows to limit result to
783          * @param   integer  offset in result to start returning rows from
784          * @return  Database_Core   This Database object.
785          */
786         public function limit($limit, $offset = NULL)
787         {
788                 $this->limit  = (int) $limit;
789
790                 if ($offset !== NULL OR ! is_int($this->offset))
791                 {
792                         $this->offset($offset);
793                 }
794
795                 return $this;
796         }
797
798         /**
799          * Sets the offset portion of a query.
800          *
801          * @param   integer  offset value
802          * @return  Database_Core   This Database object.
803          */
804         public function offset($value)
805         {
806                 $this->offset = (int) $value;
807
808                 return $this;
809         }
810
811         /**
812          * Allows key/value pairs to be set for inserting or updating.
813          *
814          * @param   string|array  key name or array of key => value pairs
815          * @param   string        value to match with key
816          * @return  Database_Core        This Database object.
817          */
818         public function set($key, $value = '')
819         {
820                 if ( ! is_array($key))
821                 {
822                         $key = array($key => $value);
823                 }
824
825                 foreach ($key as $k => $v)
826                 {
827                         // Add a table prefix if the column includes the table.
828                         if (strpos($k, '.'))
829                                 $k = $this->config['table_prefix'].$k;
830
831                         $this->set[$k] = $this->driver->escape($v);
832                 }
833
834                 return $this;
835         }
836
837         /**
838          * Compiles the select statement based on the other functions called and runs the query.
839          *
840          * @param   string  table name
841          * @param   string  limit clause
842          * @param   string  offset clause
843          * @return  Database_Result
844          */
845         public function get($table = '', $limit = NULL, $offset = NULL)
846         {
847                 if ($table != '')
848                 {
849                         $this->from($table);
850                 }
851
852                 if ( ! is_null($limit))
853                 {
854                         $this->limit($limit, $offset);
855                 }
856
857                 $sql = $this->driver->compile_select(get_object_vars($this));
858
859                 $this->reset_select();
860
861                 $result = $this->query($sql);
862
863                 $this->last_query = $sql;
864
865                 return $result;
866         }
867
868         /**
869          * Compiles the select statement based on the other functions called and runs the query.
870          *
871          * @param   string  table name
872          * @param   array   where clause
873          * @param   string  limit clause
874          * @param   string  offset clause
875          * @return  Database_Core  This Database object.
876          */
877         public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
878         {
879                 if ($table != '')
880                 {
881                         $this->from($table);
882                 }
883
884                 if ( ! is_null($where))
885                 {
886                         $this->where($where);
887                 }
888
889                 if ( ! is_null($limit))
890                 {
891                         $this->limit($limit, $offset);
892                 }
893
894                 $sql = $this->driver->compile_select(get_object_vars($this));
895
896                 $this->reset_select();
897
898                 $result = $this->query($sql);
899
900                 return $result;
901         }
902
903         /**
904          * Compiles the select statement based on the other functions called and returns the query string.
905          *
906          * @param   string  table name
907          * @param   string  limit clause
908          * @param   string  offset clause
909          * @return  string  sql string
910          */
911         public function compile($table = '', $limit = NULL, $offset = NULL)
912         {
913                 if ($table != '')
914                 {
915                         $this->from($table);
916                 }
917
918                 if ( ! is_null($limit))
919                 {
920                         $this->limit($limit, $offset);
921                 }
922
923                 $sql = $this->driver->compile_select(get_object_vars($this));
924
925                 $this->reset_select();
926
927                 return $sql;
928         }
929
930         /**
931          * Compiles an insert string and runs the query.
932          *
933          * @param   string  table name
934          * @param   array   array of key/value pairs to insert
935          * @return  Database_Result  Query result
936          */
937         public function insert($table = '', $set = NULL)
938         {
939                 if ( ! is_null($set))
940                 {
941                         $this->set($set);
942                 }
943
944                 if ($this->set == NULL)
945                         throw new Kohana_Database_Exception('database.must_use_set');
946
947                 if ($table == '')
948                 {
949                         if ( ! isset($this->from[0]))
950                                 throw new Kohana_Database_Exception('database.must_use_table');
951
952                         $table = $this->from[0];
953                 }
954
955                 // If caching is enabled, clear the cache before inserting
956                 ($this->config['cache'] === TRUE) and $this->clear_cache();
957
958                 $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
959
960                 $this->reset_write();
961
962                 return $this->query($sql);
963         }
964
965         /**
966          * Adds an "IN" condition to the where clause
967          *
968          * @param   string  Name of the column being examined
969          * @param   mixed   An array or string to match against
970          * @param   bool    Generate a NOT IN clause instead
971          * @return  Database_Core  This Database object.
972          */
973         public function in($field, $values, $not = FALSE)
974         {
975                 if (is_array($values))
976                 {
977                         $escaped_values = array();
978                         foreach ($values as $v)
979                         {
980                                 if (is_numeric($v))
981                                 {
982                                         $escaped_values[] = $v;
983                                 }
984                                 else
985                                 {
986                                         $escaped_values[] = "'".$this->driver->escape_str($v)."'";
987                                 }
988                         }
989                         $values = implode(",", $escaped_values);
990                 }
991
992                 $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
993                 $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
994
995                 return $this;
996         }
997
998         /**
999          * Adds a "NOT IN" condition to the where clause
1000          *
1001          * @param   string  Name of the column being examined
1002          * @param   mixed   An array or string to match against
1003          * @return  Database_Core  This Database object.
1004          */
1005         public function notin($field, $values)
1006         {
1007                 return $this->in($field, $values, TRUE);
1008         }
1009
1010         /**
1011          * Compiles a merge string and runs the query.
1012          *
1013          * @param   string  table name
1014          * @param   array   array of key/value pairs to merge
1015          * @return  Database_Result  Query result
1016          */
1017         public function merge($table = '', $set = NULL)
1018         {
1019                 if ( ! is_null($set))
1020                 {
1021                         $this->set($set);
1022                 }
1023
1024                 if ($this->set == NULL)
1025                         throw new Kohana_Database_Exception('database.must_use_set');
1026
1027                 if ($table == '')
1028                 {
1029                         if ( ! isset($this->from[0]))
1030                                 throw new Kohana_Database_Exception('database.must_use_table');
1031
1032                         $table = $this->from[0];
1033                 }
1034
1035                 $sql = $this->driver->merge($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
1036
1037                 $this->reset_write();
1038                 return $this->query($sql);
1039         }
1040
1041         /**
1042          * Compiles an update string and runs the query.
1043          *
1044          * @param   string  table name
1045          * @param   array   associative array of update values
1046          * @param   array   where clause
1047          * @return  Database_Result  Query result
1048          */
1049         public function update($table = '', $set = NULL, $where = NULL)
1050         {
1051                 if ( is_array($set))
1052                 {
1053                         $this->set($set);
1054                 }
1055
1056                 if ( ! is_null($where))
1057                 {
1058                         $this->where($where);
1059                 }
1060
1061                 if ($this->set == FALSE)
1062                         throw new Kohana_Database_Exception('database.must_use_set');
1063
1064                 if ($table == '')
1065                 {
1066                         if ( ! isset($this->from[0]))
1067                                 throw new Kohana_Database_Exception('database.must_use_table');
1068
1069                         $table = $this->from[0];
1070                 }
1071
1072                 $sql = $this->driver->update($this->config['table_prefix'].$table, $this->set, $this->where);
1073
1074                 $this->reset_write();
1075                 return $this->query($sql);
1076         }
1077
1078         /**
1079          * Compiles a delete string and runs the query.
1080          *
1081          * @param   string  table name
1082          * @param   array   where clause
1083          * @return  Database_Result  Query result
1084          */
1085         public function delete($table = '', $where = NULL)
1086         {
1087                 if ($table == '')
1088                 {
1089                         if ( ! isset($this->from[0]))
1090                                 throw new Kohana_Database_Exception('database.must_use_table');
1091
1092                         $table = $this->from[0];
1093                 }
1094                 else
1095                 {
1096                         $table = $this->config['table_prefix'].$table;
1097                 }
1098
1099                 if (! is_null($where))
1100                 {
1101                         $this->where($where);
1102                 }
1103
1104                 if (count($this->where) < 1)
1105                         throw new Kohana_Database_Exception('database.must_use_where');
1106
1107                 $sql = $this->driver->delete($table, $this->where);
1108
1109                 $this->reset_write();
1110                 return $this->query($sql);
1111         }
1112
1113         /**
1114          * Returns the last query run.
1115          *
1116          * @return  string SQL
1117          */
1118         public function last_query()
1119         {
1120            return $this->last_query;
1121         }
1122
1123         /**
1124          * Count query records.
1125          *
1126          * @param   string   table name
1127          * @param   array    where clause
1128          * @return  integer
1129          */
1130         public function count_records($table = FALSE, $where = NULL)
1131         {
1132                 if (count($this->from) < 1)
1133                 {
1134                         if ($table == FALSE)
1135                                 throw new Kohana_Database_Exception('database.must_use_table');
1136
1137                         $this->from($table);
1138                 }
1139
1140                 if ($where !== NULL)
1141                 {
1142                         $this->where($where);
1143                 }
1144
1145                 $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
1146
1147                 return (int) $query->current()->records_found;
1148         }
1149
1150         /**
1151          * Resets all private select variables.
1152          *
1153          * @return  void
1154          */
1155         protected function reset_select()
1156         {
1157                 $this->select   = array();
1158                 $this->from     = array();
1159                 $this->join     = array();
1160                 $this->where    = array();
1161                 $this->orderby  = array();
1162                 $this->groupby  = array();
1163                 $this->having   = array();
1164                 $this->distinct = FALSE;
1165                 $this->limit    = FALSE;
1166                 $this->offset   = FALSE;
1167         }
1168
1169         /**
1170          * Resets all private insert and update variables.
1171          *
1172          * @return  void
1173          */
1174         protected function reset_write()
1175         {
1176                 $this->set   = array();
1177                 $this->from  = array();
1178                 $this->where = array();
1179         }
1180
1181         /**
1182          * Lists all the tables in the current database.
1183          *
1184          * @return  array
1185          */
1186         public function list_tables()
1187         {
1188                 $this->link or $this->connect();
1189
1190                 return $this->driver->list_tables();
1191         }
1192
1193         /**
1194          * See if a table exists in the database.
1195          *
1196          * @param   string   table name
1197          * @param   boolean  True to attach table prefix
1198          * @return  boolean
1199          */
1200         public function table_exists($table_name, $prefix = TRUE)
1201         {
1202                 if ($prefix)
1203                         return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
1204                 else
1205                         return in_array($table_name, $this->list_tables());
1206         }
1207
1208         /**
1209          * Combine a SQL statement with the bind values. Used for safe queries.
1210          *
1211          * @param   string  query to bind to the values
1212          * @param   array   array of values to bind to the query
1213          * @return  string
1214          */
1215         public function compile_binds($sql, $binds)
1216         {
1217                 foreach ((array) $binds as $val)
1218                 {
1219                         // If the SQL contains no more bind marks ("?"), we're done.
1220                         if (($next_bind_pos = strpos($sql, '?')) === FALSE)
1221                                 break;
1222
1223                         // Properly escape the bind value.
1224                         $val = $this->driver->escape($val);
1225
1226                         // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
1227                         $val = str_replace('?', '{%B%}', $val);
1228
1229                         // Replace the first bind mark ("?") with its corresponding value.
1230                         $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
1231                 }
1232
1233                 // Restore placeholders.
1234                 return str_replace('{%B%}', '?', $sql);
1235         }
1236
1237         /**
1238          * Get the field data for a database table, along with the field's attributes.
1239          *
1240          * @param   string  table name
1241          * @return  array
1242          */
1243         public function field_data($table = '')
1244         {
1245                 $this->link or $this->connect();
1246
1247                 return $this->driver->field_data($this->config['table_prefix'].$table);
1248         }
1249
1250         /**
1251          * Get the field data for a database table, along with the field's attributes.
1252          *
1253          * @param   string  table name
1254          * @return  array
1255          */
1256         public function list_fields($table = '')
1257         {
1258                 $this->link or $this->connect();
1259
1260                 return $this->driver->list_fields($this->config['table_prefix'].$table);
1261         }
1262
1263         /**
1264          * Escapes a value for a query.
1265          *
1266          * @param   mixed   value to escape
1267          * @return  string
1268          */
1269         public function escape($value)
1270         {
1271                 return $this->driver->escape($value);
1272         }
1273
1274         /**
1275          * Escapes a string for a query.
1276          *
1277          * @param   string  string to escape
1278          * @return  string
1279          */
1280         public function escape_str($str)
1281         {
1282                 return $this->driver->escape_str($str);
1283         }
1284
1285         /**
1286          * Escapes a table name for a query.
1287          *
1288          * @param   string  string to escape
1289          * @return  string
1290          */
1291         public function escape_table($table)
1292         {
1293                 return $this->driver->escape_table($table);
1294         }
1295
1296         /**
1297          * Escapes a column name for a query.
1298          *
1299          * @param   string  string to escape
1300          * @return  string
1301          */
1302         public function escape_column($table)
1303         {
1304                 return $this->driver->escape_column($table);
1305         }
1306
1307         /**
1308          * Returns table prefix of current configuration.
1309          *
1310          * @return  string
1311          */
1312         public function table_prefix()
1313         {
1314                 return $this->config['table_prefix'];
1315         }
1316
1317         /**
1318          * Clears the query cache.
1319          *
1320          * @param   string|TRUE  clear cache by SQL statement or TRUE for last query
1321          * @return  Database_Core       This Database object.
1322          */
1323         public function clear_cache($sql = NULL)
1324         {
1325                 if ($sql === TRUE)
1326                 {
1327                         $this->driver->clear_cache($this->last_query);
1328                 }
1329                 elseif (is_string($sql))
1330                 {
1331                         $this->driver->clear_cache($sql);
1332                 }
1333                 else
1334                 {
1335                         $this->driver->clear_cache();
1336                 }
1337
1338                 return $this;
1339         }
1340
1341         /**
1342          * Pushes existing query space onto the query stack.  Use push
1343          * and pop to prevent queries from clashing before they are
1344          * executed
1345          *
1346          * @return Database_Core This Databaes object
1347          */
1348         public function push()
1349         {
1350                 array_push($this->query_history, array(
1351                         $this->select,
1352                         $this->from,
1353                         $this->join,
1354                         $this->where,
1355                         $this->orderby,
1356                         $this->order,
1357                         $this->groupby,
1358                         $this->having,
1359                         $this->distinct,
1360                         $this->limit,
1361                         $this->offset
1362                 ));
1363
1364                 $this->reset_select();
1365
1366                 return $this;
1367         }
1368
1369         /**
1370          * Pops from query stack into the current query space.
1371          *
1372          * @return Database_Core This Databaes object
1373          */
1374         public function pop()
1375         {
1376                 if (count($this->query_history) == 0)
1377                 {
1378                         // No history
1379                         return $this;
1380                 }
1381
1382                 list(
1383                         $this->select,
1384                         $this->from,
1385                         $this->join,
1386                         $this->where,
1387                         $this->orderby,
1388                         $this->order,
1389                         $this->groupby,
1390                         $this->having,
1391                         $this->distinct,
1392                         $this->limit,
1393                         $this->offset
1394                 ) = array_pop($this->query_history);
1395
1396                 return $this;
1397         }
1398
1399         /**
1400          * Count the number of records in the last query, without LIMIT or OFFSET applied.
1401          *
1402          * @return  integer
1403          */
1404         public function count_last_query()
1405         {
1406                 if ($sql = $this->last_query())
1407                 {
1408                         if (stripos($sql, 'LIMIT') !== FALSE)
1409                         {
1410                                 // Remove LIMIT from the SQL
1411                                 $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
1412                         }
1413
1414                         if (stripos($sql, 'OFFSET') !== FALSE)
1415                         {
1416                                 // Remove OFFSET from the SQL
1417                                 $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
1418                         }
1419
1420                         // Get the total rows from the last query executed
1421                         $result = $this->query
1422                         (
1423                                 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '.
1424                                 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results')
1425                         );
1426
1427                         // Return the total number of rows from the query
1428                         return (int) $result->current()->total_rows;
1429                 }
1430
1431                 return FALSE;
1432         }
1433
1434 } // End Database Class
1435
1436
1437 /**
1438  * Sets the code for a Database exception.
1439  */
1440 class Kohana_Database_Exception extends Kohana_Exception {
1441
1442         protected $code = E_DATABASE_ERROR;
1443
1444 } // End Kohana Database Exception