diff options
Diffstat (limited to 'system/libraries/Database_Builder.php')
-rw-r--r-- | system/libraries/Database_Builder.php | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php new file mode 100644 index 00000000..5a5a058c --- /dev/null +++ b/system/libraries/Database_Builder.php @@ -0,0 +1,938 @@ +<?php defined('SYSPATH') or die('No direct script access.'); +/** + * Database builder + * + * @package Kohana + * @author Kohana Team + * @copyright (c) 2008-2009 Kohana Team + * @license http://kohanaphp.com/license + */ +class Database_Builder_Core { + + // Valid ORDER BY directions + protected $order_directions = array('ASC', 'DESC', 'RAND()'); + + // Database object + protected $db; + + // Builder members + protected $select = array(); + protected $from = array(); + protected $join = array(); + protected $where = array(); + protected $group_by = array(); + protected $having = array(); + protected $order_by = array(); + protected $limit = NULL; + protected $offset = NULL; + protected $set = array(); + protected $columns = array(); + protected $values = array(); + protected $type; + + // TTL for caching (using Cache library) + protected $ttl = FALSE; + + public function __construct($db = 'default') + { + $this->db = $db; + } + + /** + * Resets all query components + */ + public function reset() + { + $this->select = array(); + $this->from = array(); + $this->join = array(); + $this->where = array(); + $this->group_by = array(); + $this->having = array(); + $this->order_by = array(); + $this->limit = NULL; + $this->offset = NULL; + $this->set = array(); + $this->values = array(); + } + + public function __toString() + { + return $this->compile(); + } + + /** + * Compiles the builder object into a SQL query + * + * @return string Compiled query + */ + protected function compile() + { + if ( ! is_object($this->db)) + { + // Use default database for compiling to string if none is given + $this->db = Database::instance($this->db); + } + + if ($this->type === Database::SELECT) + { + // SELECT columns FROM table + $sql = 'SELECT '.$this->compile_select(); + + if ( ! empty($this->from)) + { + $sql .= "\nFROM ".$this->compile_from(); + } + } + elseif ($this->type === Database::UPDATE) + { + $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set(); + } + elseif ($this->type === Database::INSERT) + { + $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values(); + } + elseif ($this->type === Database::DELETE) + { + $sql = 'DELETE FROM '.$this->compile_from(); + } + + if ( ! empty($this->join)) + { + $sql .= $this->compile_join(); + } + + if ( ! empty($this->where)) + { + $sql .= "\n".'WHERE '.$this->compile_conditions($this->where); + } + + if ( ! empty($this->having)) + { + $sql .= "\n".'HAVING '.$this->compile_conditions($this->having); + } + + if ( ! empty($this->group_by)) + { + $sql .= "\n".'GROUP BY '.$this->compile_group_by(); + } + + if ( ! empty($this->order_by)) + { + $sql .= "\nORDER BY ".$this->compile_order_by(); + } + + if (is_int($this->limit)) + { + $sql .= "\nLIMIT ".$this->limit; + } + + if (is_int($this->offset)) + { + $sql .= "\nOFFSET ".$this->offset; + } + + return $sql; + } + + /** + * Compiles the SELECT portion of the query + * + * @return string + */ + protected function compile_select() + { + $vals = array(); + + foreach ($this->select as $alias => $name) + { + if (is_string($alias)) + { + $vals[] = $this->db->quote_column($name, $alias); + } + else + { + $vals[] = $this->db->quote_column($name); + } + } + + return implode(', ', $vals); + } + + /** + * Compiles the FROM portion of the query + * + * @return string + */ + protected function compile_from() + { + $vals = array(); + + foreach ($this->from as $alias => $name) + { + if (is_string($alias)) + { + // Using AS format so escape both + $vals[] = $this->db->quote_table($name, $alias); + } + else + { + // Just using the table name itself + $vals[] = $this->db->quote_table($name); + } + } + + return implode(', ', $vals); + } + + /** + * Compiles the JOIN portion of the query + * + * @return string + */ + protected function compile_join() + { + $sql = ''; + foreach ($this->join as $join) + { + list($table, $keys, $type) = $join; + + if ($type !== NULL) + { + // Join type + $sql .= ' '.$type; + } + + $sql .= ' JOIN '.$this->db->quote_table($table); + + $condition = ''; + if ($keys instanceof Database_Expression) + { + $condition = (string) $keys; + } + elseif (is_array($keys)) + { + // ON condition is an array of matches + foreach ($keys as $key => $value) + { + if ( ! empty($condition)) + { + $condition .= ' AND '; + } + + $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value); + } + } + + if ( ! empty($condition)) + { + // Add ON condition + $sql .= ' ON ('.$condition.')'; + } + } + + return $sql; + } + + /** + * Compiles the GROUP BY portion of the query + * + * @return string + */ + protected function compile_group_by() + { + $vals = array(); + + foreach ($this->group_by as $column) + { + // Escape the column + $vals[] = $this->db->quote_column($column); + } + + return implode(', ', $vals); + } + + /** + * Compiles the ORDER BY portion of the query + * + * @return string + */ + public function compile_order_by() + { + $ordering = array(); + + foreach ($this->order_by as $column => $order_by) + { + list($column, $direction) = each($order_by); + + $column = $this->db->quote_column($column); + + if ($direction !== NULL) + { + $direction = ' '.$direction; + } + + $ordering[] = $column.$direction; + } + + return implode(', ', $ordering); + } + + /** + * Compiles the SET portion of the query for UPDATE + * + * @return string + */ + public function compile_set() + { + $vals = array(); + + foreach ($this->set as $key => $value) + { + // Using an UPDATE so Key = Val + $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value); + } + + return implode(', ', $vals); + } + + /** + * Join tables to the builder + * + * @param mixed Table name + * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression) + * @param mixed Value if $keys is not an array or Database_Expression + * @param string Join type (LEFT, RIGHT, INNER, etc.) + * @return Database_Builder + */ + public function join($table, $keys, $value = NULL, $type = NULL) + { + if (is_string($keys)) + { + $keys = array($keys => $value); + } + + if ($type !== NULL) + { + $type = strtoupper($type); + } + + $this->join[] = array($table, $keys, $type); + + return $this; + } + + /** + * Add tables to the FROM portion of the builder + * + * @param string|array table name or array(alias => table) + * @return Database_Builder + */ + public function from($tables) + { + if ( ! is_array($tables)) + { + $tables = func_get_args(); + } + + $this->from = array_merge($this->from, $tables); + + return $this; + } + + /** + * Add fields to the GROUP BY portion + * + * @param mixed Field names or an array of fields + * @return Database_Builder + */ + public function group_by($columns) + { + if ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->group_by = array_merge($this->group_by, $columns); + + return $this; + } + + /** + * Add conditions to the HAVING clause (AND) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function having($columns, $op = '=', $value = NULL) + { + return $this->and_having($columns, $op, $value); + } + + /** + * Add conditions to the HAVING clause (AND) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_having($columns, $op = '=', $value = NULL) + { + $this->having[] = array('AND' => array($columns, $op, $value)); + return $this; + } + + /** + * Add conditions to the HAVING clause (OR) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_having($columns, $op = '=', $value = NULL) + { + $this->having[] = array('OR' => array($columns, $op, $value)); + return $this; + } + + /** + * Add fields to the ORDER BY portion + * + * @param mixed Field names or an array of fields (field => direction) + * @param string Direction or NULL for ascending + * @return Database_Builder + */ + public function order_by($columns, $direction = NULL) + { + if (is_string($columns)) + { + $columns = array($columns => $direction); + } + + $this->order_by[] = $columns; + + return $this; + } + + /** + * Limit rows returned + * + * @param int Number of rows + * @return Database_Builder + */ + public function limit($number) + { + $this->limit = (int) $number; + + return $this; + } + + /** + * Offset into result set + * + * @param int Offset + * @return Database_Builder + */ + public function offset($number) + { + $this->offset = (int) $number; + + return $this; + } + + public function left_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT'); + } + + public function right_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT'); + } + + public function inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'INNER'); + } + + public function outer_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'OUTER'); + } + + public function full_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'FULL'); + } + + public function left_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'LEFT INNER'); + } + + public function right_inner_join($table, $keys, $value = NULL) + { + return $this->join($table, $keys, $value, 'RIGHT INNER'); + } + + public function open($clause = 'WHERE') + { + return $this->and_open($clause); + } + + public function and_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('AND' => '('); + } + else + { + $this->having[] = array('AND' => '('); + } + + return $this; + } + + public function or_open($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array('OR' => '('); + } + else + { + $this->having[] = array('OR' => '('); + } + + return $this; + } + + public function close($clause = 'WHERE') + { + if ($clause === 'WHERE') + { + $this->where[] = array(')'); + } + else + { + $this->having[] = array(')'); + } + + return $this; + } + + /** + * Add conditions to the WHERE clause (AND) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function where($columns, $op = '=', $value = NULL) + { + return $this->and_where($columns, $op, $value); + } + + /** + * Add conditions to the WHERE clause (AND) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function and_where($columns, $op = '=', $value = NULL) + { + $this->where[] = array('AND' => array($columns, $op, $value)); + return $this; + } + + /** + * Add conditions to the WHERE clause (OR) + * + * @param mixed Column name or array of columns => vals + * @param string Operation to perform + * @param mixed Value + * @return Database_Builder + */ + public function or_where($columns, $op = '=', $value = NULL) + { + $this->where[] = array('OR' => array($columns, $op, $value)); + return $this; + } + + /** + * Compiles the given clause's conditions + * + * @param array Clause conditions + * @return string + */ + protected function compile_conditions($groups) + { + $last_condition = NULL; + + $sql = ''; + foreach ($groups as $group) + { + // Process groups of conditions + foreach ($group as $logic => $condition) + { + if ($condition === '(') + { + if ( ! empty($sql) AND $last_condition !== '(') + { + // Include logic operator + $sql .= ' '.$logic.' '; + } + + $sql .= '('; + } + elseif ($condition === ')') + { + $sql .= ')'; + } + else + { + list($columns, $op, $value) = $condition; + + // Stores each individual condition + $vals = array(); + + if ($columns instanceof Database_Expression) + { + // Add directly to condition list + $vals[] = (string) $columns; + } + else + { + $op = strtoupper($op); + + if ( ! is_array($columns)) + { + $columns = array($columns => $value); + } + + foreach ($columns as $column => $value) + { + if ($value instanceof Database_Builder) + { + // Using a subquery + $value->db = $this->db; + $value = '('.(string) $value.')'; + } + elseif (is_array($value)) + { + if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN') + { + // Falls between two values + $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]); + } + else + { + // Return as list + $value = array_map(array($this->db, 'quote'), $value); + $value = '('.implode(', ', $value).')'; + } + } + else + { + $value = $this->db->quote($value); + } + + if ( ! empty($column)) + { + // Ignore blank columns + $column = $this->db->quote_column($column); + } + + // Add to condition list + $vals[] = $column.' '.$op.' '.$value; + } + } + + if ( ! empty($sql) AND $last_condition !== '(') + { + // Add the logic operator + $sql .= ' '.$logic.' '; + } + + // Join the condition list items together by the given logic operator + $sql .= implode(' '.$logic.' ', $vals); + } + + $last_condition = $condition; + } + } + + return $sql; + } + + /** + * Set values for UPDATE + * + * @param mixed Column name or array of columns => vals + * @param mixed Value (can be a Database_Expression) + * @return Database_Builder + */ + public function set($keys, $value = NULL) + { + if (is_string($keys)) + { + $keys = array($keys => $value); + } + + $this->set = array_merge($keys, $this->set); + + return $this; + } + + /** + * Columns used for INSERT queries + * + * @param array Columns + * @return Database_Builder + */ + public function columns($columns) + { + if ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->columns = $columns; + + return $this; + } + + /** + * Compiles the columns portion of the query for INSERT + * + * @return string + */ + protected function compile_columns() + { + return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')'; + } + + /** + * Values used for INSERT queries + * + * @param array Values + * @return Database_Builder + */ + public function values($values) + { + if ( ! is_array($values)) + { + $values = func_get_args(); + } + + $this->values[] = $values; + + return $this; + } + + /** + * Compiles the VALUES portion of the query for INSERT + * + * @return string + */ + protected function compile_values() + { + $values = array(); + foreach ($this->values as $group) + { + // Each set of values to be inserted + $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')'; + } + + return implode(', ', $values); + } + + /** + * Create a SELECT query and specify selected columns + * + * @param string|array column name or array(alias => column) + * @return Database_Builder + */ + public function select($columns = NULL) + { + $this->type = Database::SELECT; + + if ($columns === NULL) + { + $columns = array('*'); + } + elseif ( ! is_array($columns)) + { + $columns = func_get_args(); + } + + $this->select = array_merge($this->select, $columns); + + return $this; + } + + /** + * Create an UPDATE query + * + * @param string Table name + * @param array Array of Keys => Values + * @param array WHERE conditions + * @return Database_Builder + */ + public function update($table = NULL, $set = NULL, $where = NULL) + { + $this->type = Database::UPDATE; + + if (is_array($set)) + { + $this->set($set); + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts + * + * @param string Table name + * @param array Array of Keys => Values + * @return Database_Builder + */ + public function insert($table = NULL, $set = NULL) + { + $this->type = Database::INSERT; + + if (is_array($set)) + { + $this->columns(array_keys($set)); + $this->values(array_values($set)); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Create a DELETE query + * + * @param string Table name + * @param array WHERE conditions + * @return Database_Builder + */ + public function delete($table, $where = NULL) + { + $this->type = Database::DELETE; + + if ($where !== NULL) + { + $this->where($where); + } + + if ($table !== NULL) + { + $this->from($table); + } + + return $this; + } + + /** + * Count records for a given table + * + * @param string Table name + * @param array WHERE conditions + * @return int + */ + public function count_records($table = FALSE, $where = NULL) + { + if (count($this->from) < 1) + { + if ($table === FALSE) + throw new Database_Exception('Database count_records requires a table'); + + $this->from($table); + } + + if ($where !== NULL) + { + $this->where($where); + } + + // Grab the count AS records_found + $result = $this->select(array('records_found' => 'COUNT("*")'))->execute(); + + return $result->get('records_found'); + } + + /** + * Executes the built query + * + * @param mixed Database name or object + * @return Database_Result + */ + public function execute($db = NULL) + { + if ($db !== NULL) + { + $this->db = $db; + } + + if ( ! is_object($this->db)) + { + // Get the database instance + $this->db = Database::instance($this->db); + } + + $query = $this->compile(); + + // Reset the query after executing + $this->reset(); + + if ($this->ttl !== FALSE AND $this->type === Database::SELECT) + { + // Return result from cache (only allowed with SELECT) + return $this->db->query_cache($query, $this->ttl); + } + else + { + // Load the result (no caching) + return $this->db->query($query); + } + } + + /** + * Set caching for the query + * + * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise) + * @return Database_Builder + */ + public function cache($ttl = NULL) + { + $this->ttl = $ttl; + + return $this; + } + +} // End Database_Builder |