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 */
12class 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 */
1440class Kohana_Database_Exception extends Kohana_Exception {
1441
1442	protected $code = E_DATABASE_ERROR;
1443
1444} // End Kohana Database Exception
1445