summaryrefslogtreecommitdiff
path: root/system/libraries/Database.php
diff options
context:
space:
mode:
authorTim Almdal <tnalmdal@shaw.ca>2009-12-26 11:24:50 -0800
committerTim Almdal <tnalmdal@shaw.ca>2009-12-26 11:24:50 -0800
commit3060a6f662da66008d57a461bf1c9b5b4aa2b002 (patch)
tree442fd290505817efc0324f2af6e01805cb7396aa /system/libraries/Database.php
parent1cd6a615bb47a33794e4a4f690c87a348ab752d7 (diff)
parent32d25dafd5b033338b6a9bb8c7c53edab462543a (diff)
Merge branch 'master' into talmdal_dev
Conflicts: modules/gallery/controllers/albums.php modules/gallery/controllers/movies.php modules/gallery/controllers/photos.php
Diffstat (limited to 'system/libraries/Database.php')
-rw-r--r--system/libraries/Database.php1588
1 files changed, 388 insertions, 1200 deletions
diff --git a/system/libraries/Database.php b/system/libraries/Database.php
index 4cd29c58..38a38fbf 100644
--- a/system/libraries/Database.php
+++ b/system/libraries/Database.php
@@ -1,1457 +1,645 @@
-<?php defined('SYSPATH') OR die('No direct access allowed.');
+<?php defined('SYSPATH') or die('No direct script access.');
/**
- * Provides database access in a platform agnostic way, using simple query building blocks.
+ * Database wrapper.
*
- * $Id: Database.php 4438 2009-07-06 04:11:16Z kiall $
+ * $Id: Database.php 4709 2009-12-10 05:09:35Z isaiah $
*
- * @package Core
+ * @package Kohana
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
-class Database_Core {
+abstract class Database_Core {
- // Database instances
- public static $instances = array();
+ const SELECT = 1;
+ const INSERT = 2;
+ const UPDATE = 3;
+ const DELETE = 4;
+ const CROSS_REQUEST = 5;
+ const PER_REQUEST = 6;
- // Global benchmark
+ protected static $instances = array();
+
+ // Global benchmarks
public static $benchmarks = array();
- // Configuration
- protected $config = array
- (
- 'benchmark' => TRUE,
- 'persistent' => FALSE,
- 'connection' => '',
- 'character_set' => 'utf8',
- 'table_prefix' => '',
- 'object' => TRUE,
- 'cache' => FALSE,
- 'escape' => TRUE,
- );
-
- // Database driver object
- protected $driver;
- protected $link;
-
- // Un-compiled parts of the SQL query
- protected $select = array();
- protected $set = array();
- protected $from = array();
- protected $join = array();
- protected $where = array();
- protected $orderby = array();
- protected $order = array();
- protected $groupby = array();
- protected $having = array();
- protected $distinct = FALSE;
- protected $limit = FALSE;
- protected $offset = FALSE;
- protected $last_query = '';
-
- // Stack of queries for push/pop
- protected $query_history = array();
+ // Last execute query
+ protected $last_query;
+
+ // Configuration array
+ protected $config;
+
+ // Required configuration keys
+ protected $config_required = array();
+
+ // Raw server connection
+ protected $connection;
+
+ // Cache (Cache object for cross-request, array for per-request)
+ protected $cache;
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '"';
/**
* Returns a singleton instance of Database.
*
- * @param mixed configuration array or DSN
+ * @param string Database name
* @return Database_Core
*/
- public static function & instance($name = 'default', $config = NULL)
+ public static function instance($name = 'default')
{
if ( ! isset(Database::$instances[$name]))
{
- // Create a new instance
- Database::$instances[$name] = new Database($config === NULL ? $name : $config);
+ // Load the configuration for this database group
+ $config = Kohana::config('database.'.$name);
+
+ if (is_string($config['connection']))
+ {
+ // Parse the DSN into connection array
+ $config['connection'] = Database::parse_dsn($config['connection']);
+ }
+
+ // Set the driver class name
+ $driver = 'Database_'.ucfirst($config['connection']['type']);
+
+ // Create the database connection instance
+ Database::$instances[$name] = new $driver($config);
}
return Database::$instances[$name];
}
/**
- * Returns the name of a given database instance.
- *
- * @param Database instance of Database
- * @return string
- */
- public static function instance_name(Database $db)
- {
- return array_search($db, Database::$instances, TRUE);
- }
-
- /**
- * Sets up the database configuration, loads the Database_Driver.
+ * Constructs a new Database object
*
- * @throws Kohana_Database_Exception
+ * @param array Database config array
+ * @return Database_Core
*/
- public function __construct($config = array())
+ protected function __construct(array $config)
{
- if (empty($config))
- {
- // Load the default group
- $config = Kohana::config('database.default');
- }
- elseif (is_array($config) AND count($config) > 0)
- {
- if ( ! array_key_exists('connection', $config))
- {
- $config = array('connection' => $config);
- }
- }
- elseif (is_string($config))
- {
- // The config is a DSN string
- if (strpos($config, '://') !== FALSE)
- {
- $config = array('connection' => $config);
- }
- // The config is a group name
- else
- {
- $name = $config;
-
- // Test the config group name
- if (($config = Kohana::config('database.'.$config)) === NULL)
- throw new Kohana_Database_Exception('database.undefined_group', $name);
- }
- }
+ // Store the config locally
+ $this->config = $config;
- // Merge the default config with the passed config
- $this->config = array_merge($this->config, $config);
-
- if (is_string($this->config['connection']))
+ if ($this->config['cache'] !== FALSE)
{
- // Make sure the connection is valid
- if (strpos($this->config['connection'], '://') === FALSE)
- throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
-
- // Parse the DSN, creating an array to hold the connection parameters
- $db = array
- (
- 'type' => FALSE,
- 'user' => FALSE,
- 'pass' => FALSE,
- 'host' => FALSE,
- 'port' => FALSE,
- 'socket' => FALSE,
- 'database' => FALSE
- );
-
- // Get the protocol and arguments
- list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
-
- if (strpos($connection, '@') !== FALSE)
+ if (is_string($this->config['cache']))
{
- // Get the username and password
- list ($db['pass'], $connection) = explode('@', $connection, 2);
- // Check if a password is supplied
- $logindata = explode(':', $db['pass'], 2);
- $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
- $db['user'] = $logindata[0];
-
- // Prepare for finding the database
- $connection = explode('/', $connection);
-
- // Find the database name
- $db['database'] = array_pop($connection);
-
- // Reset connection string
- $connection = implode('/', $connection);
-
- // Find the socket
- if (preg_match('/^unix\([^)]++\)/', $connection))
- {
- // This one is a little hairy: we explode based on the end of
- // the socket, removing the 'unix(' from the connection string
- list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
- }
- elseif (strpos($connection, ':') !== FALSE)
- {
- // Fetch the host and port name
- list ($db['host'], $db['port']) = explode(':', $connection, 2);
- }
- else
- {
- $db['host'] = $connection;
- }
+ // Use Cache library
+ $this->cache = new Cache($this->config['cache']);
}
- else
+ elseif ($this->config['cache'] === TRUE)
{
- // File connection
- $connection = explode('/', $connection);
-
- // Find database file name
- $db['database'] = array_pop($connection);
-
- // Find database directory name
- $db['socket'] = implode('/', $connection).'/';
+ // Use array
+ $this->cache = array();
}
-
- // Reset the connection array to the database config
- $this->config['connection'] = $db;
}
- // Set driver name
- $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
-
- // Load the driver
- if ( ! Kohana::auto_load($driver))
- throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
-
- // Initialize the driver
- $this->driver = new $driver($this->config);
-
- // Validate the driver
- if ( ! ($this->driver instanceof Database_Driver))
- throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
+ }
- Kohana::log('debug', 'Database Library initialized');
+ public function __destruct()
+ {
+ $this->disconnect();
}
/**
- * Simple connect method to get the database queries up and running.
+ * Connects to the database
*
- * @return void
+ * @return void
*/
- public function connect()
- {
- // A link can be a resource or an object
- if ( ! is_resource($this->link) AND ! is_object($this->link))
- {
- $this->link = $this->driver->connect();
- if ( ! is_resource($this->link) AND ! is_object($this->link))
- throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
-
- // Clear password after successful connect
- $this->config['connection']['pass'] = NULL;
- }
- }
+ abstract public function connect();
/**
- * Runs a query into the driver and returns the result.
+ * Disconnects from the database
*
- * @param string SQL query to execute
- * @return Database_Result
+ * @return void
*/
- public function query($sql = '')
- {
- if ($sql == '') return FALSE;
-
- // No link? Connect!
- $this->link or $this->connect();
-
- // Start the benchmark
- $start = microtime(TRUE);
-
- if (func_num_args() > 1) //if we have more than one argument ($sql)
- {
- $argv = func_get_args();
- $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
- }
-
- // Compile binds if needed
- if (isset($binds))
- {
- $sql = $this->compile_binds($sql, $binds);
- }
-
- // Fetch the result
- $result = $this->driver->query($this->last_query = $sql);
-
- // Stop the benchmark
- $stop = microtime(TRUE);
-
- if ($this->config['benchmark'] == TRUE)
- {
- // Benchmark the query
- Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
- }
-
- return $result;
- }
+ abstract public function disconnect();
/**
- * Selects the column names for a database query.
+ * Sets the character set
*
- * @param string string or array of column names to select
- * @return Database_Core This Database object.
+ * @return void
*/
- public function select($sql = '*')
- {
- if (func_num_args() > 1)
- {
- $sql = func_get_args();
- }
- elseif (is_string($sql))
- {
- $sql = explode(',', $sql);
- }
- else
- {
- $sql = (array) $sql;
- }
-
- foreach ($sql as $val)
- {
- if (($val = trim($val)) === '') continue;
-
- if (strpos($val, '(') === FALSE AND $val !== '*')
- {
- if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
- {
- // Only prepend with table prefix if table name is specified
- $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1];
-
- $this->distinct = TRUE;
- }
- else
- {
- $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
- }
-
- $val = $this->driver->escape_column($val);
- }
-
- $this->select[] = $val;
- }
-
- return $this;
- }
+ abstract public function set_charset($charset);
/**
- * Selects the from table(s) for a database query.
+ * Executes the query
*
- * @param string string or array of tables to select
- * @return Database_Core This Database object.
+ * @param string SQL
+ * @return Database_Result
*/
- public function from($sql)
- {
- if (func_num_args() > 1)
- {
- $sql = func_get_args();
- }
- elseif (is_string($sql))
- {
- $sql = explode(',', $sql);
- }
- else
- {
- $sql = array($sql);
- }
+ abstract public function query_execute($sql);
- foreach ($sql as $val)
- {
- if (is_string($val))
- {
- if (($val = trim($val)) === '') continue;
-
- // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
- if (stripos($val, ' AS ') !== FALSE)
- {
- $val = str_ireplace(' AS ', ' AS ', $val);
-
- list($table, $alias) = explode(' AS ', $val);
+ /**
+ * Escapes the given value
+ *
+ * @param mixed Value
+ * @return mixed Escaped value
+ */
+ abstract public function escape($value);
- // Attach prefix to both sides of the AS
- $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
- }
- else
- {
- $val = $this->config['table_prefix'].$val;
- }
- }
+ /**
+ * List constraints for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_constraints($table);
- $this->from[] = $val;
- }
+ /**
+ * List fields for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_fields($table);
- return $this;
- }
+ /**
+ * List tables for the given connection (checks for prefix)
+ *
+ * @return array
+ */
+ abstract public function list_tables();
/**
- * Generates the JOIN portion of the query.
+ * Converts the given DSN string to an array of database connection components
*
- * @param string table name
- * @param string|array where key or array of key => value pairs
- * @param string where value
- * @param string type of join
- * @return Database_Core This Database object.
+ * @param string DSN string
+ * @return array
*/
- public function join($table, $key, $value = NULL, $type = '')
+ public static function parse_dsn($dsn)
{
- $join = array();
+ $db = array
+ (
+ 'type' => FALSE,
+ 'user' => FALSE,
+ 'pass' => FALSE,
+ 'host' => FALSE,
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => FALSE
+ );
- if ( ! empty($type))
+ // Get the protocol and arguments
+ list ($db['type'], $connection) = explode('://', $dsn, 2);
+
+ if ($connection[0] === '/')
{
- $type = strtoupper(trim($type));
+ // Strip leading slash
+ $db['database'] = substr($connection, 1);
+ }
+ else
+ {
+ $connection = parse_url('http://'.$connection);
- if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+ if (isset($connection['user']))
{
- $type = '';
+ $db['user'] = $connection['user'];
}
- else
+
+ if (isset($connection['pass']))
{
- $type .= ' ';
+ $db['pass'] = $connection['pass'];
}
- }
- $cond = array();
- $keys = is_array($key) ? $key : array($key => $value);
- foreach ($keys as $key => $value)
- {
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
-
- if (is_string($value))
+ if (isset($connection['port']))
{
- // Only escape if it's a string
- $value = $this->driver->escape_column($this->config['table_prefix'].$value);
+ $db['port'] = $connection['port'];
}
- $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE);
- }
-
- if ( ! is_array($this->join))
- {
- $this->join = array();
- }
-
- if ( ! is_array($table))
- {
- $table = array($table);
- }
-
- foreach ($table as $t)
- {
- if (is_string($t))
+ if (isset($connection['host']))
{
- // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
- if (stripos($t, ' AS ') !== FALSE)
+ if ($connection['host'] === 'unix(')
{
- $t = str_ireplace(' AS ', ' AS ', $t);
-
- list($table, $alias) = explode(' AS ', $t);
-
- // Attach prefix to both sides of the AS
- $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
+ list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2);
}
else
{
- $t = $this->config['table_prefix'].$t;
+ $db['host'] = $connection['host'];
}
}
- $join['tables'][] = $this->driver->escape_column($t);
+ if (isset($connection['path']) AND $connection['path'])
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection['path'], 1);
+ }
}
- $join['conditions'] = '('.trim(implode(' ', $cond)).')';
- $join['type'] = $type;
-
- $this->join[] = $join;
-
- return $this;
+ return $db;
}
-
/**
- * Selects the where(s) for a database query.
+ * Returns the last executed query for this database
*
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
+ * @return string
*/
- public function where($key, $value = NULL, $quote = TRUE)
+ public function last_query()
{
- $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
- if (is_object($key))
- {
- $keys = array((string) $key => '');
- }
- elseif ( ! is_array($key))
- {
- $keys = array($key => $value);
- }
- else
- {
- $keys = $key;
- }
-
- foreach ($keys as $key => $value)
- {
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
- $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
- }
-
- return $this;
+ return $this->last_query;
}
/**
- * Selects the or where(s) for a database query.
+ * Executes the given query, returning the cached version if enabled
*
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
+ * @param string SQL query
+ * @return Database_Result
*/
- public function orwhere($key, $value = NULL, $quote = TRUE)
+ public function query($sql)
{
- $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
- if (is_object($key))
- {
- $keys = array((string) $key => '');
- }
- elseif ( ! is_array($key))
- {
- $keys = array($key => $value);
- }
- else
- {
- $keys = $key;
- }
+ // Start the benchmark
+ $start = microtime(TRUE);
- foreach ($keys as $key => $value)
+ if (is_array($this->cache))
{
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
- $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
- }
-
- return $this;
- }
+ $hash = $this->query_hash($sql);
- /**
- * Selects the like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
- */
- public function like($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ if (isset($this->cache[$hash]))
+ {
+ // Use cached result
+ $result = $this->cache[$hash];
- foreach ($fields as $field => $match)
- {
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
+ // It's from cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // No cache, execute query and store in cache
+ $result = $this->cache[$hash] = $this->query_execute($sql);
+ }
}
-
- return $this;
- }
-
- /**
- * Selects the or like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
- */
- public function orlike($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
-
- foreach ($fields as $field => $match)
+ else
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
+ // Execute the query, cache is off
+ $result = $this->query_execute($sql);
}
- return $this;
- }
-
- /**
- * Selects the not like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
- */
- public function notlike($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ // Stop the benchmark
+ $stop = microtime(TRUE);
- foreach ($fields as $field => $match)
+ if ($this->config['benchmark'] === TRUE)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
}
- return $this;
+ return $result;
}
/**
- * Selects the or not like(s) for a database query.
+ * Performs the query on the cache (and caches it if it's not found)
*
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
+ * @param string query
+ * @param int time-to-live (NULL for Cache default)
+ * @return Database_Cache_Result
*/
- public function ornotlike($field, $match = '', $auto = TRUE)
+ public function query_cache($sql, $ttl)
{
- $fields = is_array($field) ? $field : array($field => $match);
-
- foreach ($fields as $field => $match)
+ if ( ! $this->cache instanceof Cache)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
+ throw new Database_Exception('Database :name has not been configured to use the Cache library.');
}
- return $this;
- }
+ // Start the benchmark
+ $start = microtime(TRUE);
- /**
- * Selects the like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
- */
- public function regex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ $hash = $this->query_hash($sql);
- foreach ($fields as $field => $match)
+ if (($data = $this->cache->get($hash)) !== NULL)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
- }
-
- return $this;
- }
+ // Found in cache, create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
- /**
- * Selects the or like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
- */
- public function orregex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
-
- foreach ($fields as $field => $match)
- {
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
+ // It's from the cache
+ $sql .= ' [CACHE]';
}
-
- return $this;
- }
-
- /**
- * Selects the not regex(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string regex value to match with field
- * @return Database_Core This Database object.
- */
- public function notregex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
-
- foreach ($fields as $field => $match)
+ else
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
- }
+ // Run the query and return the full array of rows
+ $data = $this->query_execute($sql)->as_array(TRUE);
- return $this;
- }
+ // Set the Cache
+ $this->cache->set($hash, $data, NULL, $ttl);
- /**
- * Selects the or not regex(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string regex value to match with field
- * @return Database_Core This Database object.
- */
- public function ornotregex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
-
- foreach ($fields as $field => $match)
- {
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
+ // Create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
}
- return $this;
- }
-
- /**
- * Chooses the column to group by in a select query.
- *
- * @param string column name to group by
- * @return Database_Core This Database object.
- */
- public function groupby($by)
- {
- if ( ! is_array($by))
- {
- $by = explode(',', (string) $by);
- }
+ // Stop the benchmark
+ $stop = microtime(TRUE);
- foreach ($by as $val)
+ if ($this->config['benchmark'] === TRUE)
{
- $val = trim($val);
-
- if ($val != '')
- {
- // Add the table prefix if we are using table.column names
- if(strpos($val, '.'))
- {
- $val = $this->config['table_prefix'].$val;
- }
-
- $this->groupby[] = $this->driver->escape_column($val);
- }
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
}
- return $this;
- }
-
- /**
- * Selects the having(s) for a database query.
- *
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
- */
- public function having($key, $value = '', $quote = TRUE)
- {
- $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
- return $this;
+ return $result;
}
/**
- * Selects the or having(s) for a database query.
+ * Generates a hash for the given query
*
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
+ * @param string SQL query string
+ * @return string
*/
- public function orhaving($key, $value = '', $quote = TRUE)
+ protected function query_hash($sql)
{
- $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
- return $this;
+ return sha1(str_replace("\n", ' ', trim($sql)));
}
/**
- * Chooses which column(s) to order the select query by.
+ * Clears the internal query cache.
*
- * @param string|array column(s) to order on, can be an array, single column, or comma seperated list of columns
- * @param string direction of the order
- * @return Database_Core This Database object.
+ * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return Database
*/
- public function orderby($orderby, $direction = NULL)
+ public function clear_cache($sql = NULL, $type = NULL)
{
- if ( ! is_array($orderby))
+ if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST))
{
- $orderby = array($orderby => $direction);
+ // Using cross-request Cache library
+ if ($sql === TRUE)
+ {
+ $this->cache->delete($this->query_hash($this->last_query));
+ }
+ elseif (is_string($sql))
+ {
+ $this->cache->delete($this->query_hash($sql));
+ }
+ else
+ {
+ $this->cache->delete_all();
+ }
}
-
- foreach ($orderby as $column => $direction)
+ elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST))
{
- $direction = strtoupper(trim($direction));
-
- // Add a direction if the provided one isn't valid
- if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
+ // Using per-request memory cache
+ if ($sql === TRUE)
{
- $direction = 'ASC';
+ unset($this->cache[$this->query_hash($this->last_query)]);
}
-
- // Add the table prefix if a table.column was passed
- if (strpos($column, '.'))
+ elseif (is_string($sql))
{
- $column = $this->config['table_prefix'].$column;
+ unset($this->cache[$this->query_hash($sql)]);
+ }
+ else
+ {
+ $this->cache = array();
}
-
- $this->orderby[] = $this->driver->escape_column($column).' '.$direction;
}
-
- return $this;
}
/**
- * Selects the limit section of a query.
+ * Quotes the given value
*
- * @param integer number of rows to limit result to
- * @param integer offset in result to start returning rows from
- * @return Database_Core This Database object.
+ * @param mixed value
+ * @return mixed
*/
- public function limit($limit, $offset = NULL)
+ public function quote($value)
{
- $this->limit = (int) $limit;
+ if ( ! $this->config['escape'])
+ return $value;
- if ($offset !== NULL OR ! is_int($this->offset))
+ if ($value === NULL)
{
- $this->offset($offset);
+ return 'NULL';
}
-
- return $this;
- }
-
- /**
- * Sets the offset portion of a query.
- *
- * @param integer offset value
- * @return Database_Core This Database object.
- */
- public function offset($value)
- {
- $this->offset = (int) $value;
-
- return $this;
- }
-
- /**
- * Allows key/value pairs to be set for inserting or updating.
- *
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @return Database_Core This Database object.
- */
- public function set($key, $value = '')
- {
- if ( ! is_array($key))
+ elseif ($value === TRUE)
{
- $key = array($key => $value);
+ return 'TRUE';
}
-
- foreach ($key as $k => $v)
+ elseif ($value === FALSE)
{
- // Add a table prefix if the column includes the table.
- if (strpos($k, '.'))
- $k = $this->config['table_prefix'].$k;
-
- $this->set[$k] = $this->driver->escape($v);
+ return 'FALSE';
}
-
- return $this;
- }
-
- /**
- * Compiles the select statement based on the other functions called and runs the query.
- *
- * @param string table name
- * @param string limit clause
- * @param string offset clause
- * @return Database_Result
- */
- public function get($table = '', $limit = NULL, $offset = NULL)
- {
- if ($table != '')
+ elseif (is_int($value))
{
- $this->from($table);
+ return (int) $value;
}
-
- if ( ! is_null($limit))
+ elseif ($value instanceof Database_Expression)
{
- $this->limit($limit, $offset);
+ return (string) $value;
}
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- $result = $this->query($sql);
-
- $this->last_query = $sql;
-
- return $result;
+ return '\''.$this->escape($value).'\'';
}
/**
- * Compiles the select statement based on the other functions called and runs the query.
+ * Quotes a table, adding the table prefix
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
*
- * @param string table name
- * @param array where clause
- * @param string limit clause
- * @param string offset clause
- * @return Database_Core This Database object.
+ * @param string|array table name or array - 'users u' or array('u' => 'users') both valid
+ * @param string table alias
+ * @return string
*/
- public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
+ public function quote_table($table, $alias = NULL)
{
- if ($table != '')
+ if (is_array($table))
{
- $this->from($table);
+ // Using array('u' => 'user')
+ list($alias, $table) = each($table);
}
-
- if ( ! is_null($where))
+ elseif (strpos(' ', $table) !== FALSE)
{
- $this->where($where);
+ // Using format 'user u'
+ list($table, $alias) = explode(' ', $table);
}
- if ( ! is_null($limit))
+ if ($table instanceof Database_Expression)
{
- $this->limit($limit, $offset);
- }
-
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- $result = $this->query($sql);
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
- return $result;
- }
+ return $table.' AS '.$alias;
+ }
- /**
- * Compiles the select statement based on the other functions called and returns the query string.
- *
- * @param string table name
- * @param string limit clause
- * @param string offset clause
- * @return string sql string
- */
- public function compile($table = '', $limit = NULL, $offset = NULL)
- {
- if ($table != '')
- {
- $this->from($table);
+ return (string) $table;
}
- if ( ! is_null($limit))
+ if ($this->config['table_prefix'])
{
- $this->limit($limit, $offset);
- }
-
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- return $sql;
- }
-
- /**
- * Compiles an insert string and runs the query.
- *
- * @param string table name
- * @param array array of key/value pairs to insert
- * @return Database_Result Query result
- */
- public function insert($table = '', $set = NULL)
- {
- if ( ! is_null($set))
- {
- $this->set($set);
+ $table = $this->config['table_prefix'].$table;
}
- if ($this->set == NULL)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
+ if ($alias)
{
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ $alias = $this->quote.$alias.$this->quote;
+ }
- $table = $this->from[0];
+ return $table.' AS '.$alias;
}
- // If caching is enabled, clear the cache before inserting
- ($this->config['cache'] === TRUE) and $this->clear_cache();
-
- $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
-
- $this->reset_write();
-
- return $this->query($sql);
- }
-
- /**
- * Adds an "IN" condition to the where clause
- *
- * @param string Name of the column being examined
- * @param mixed An array or string to match against
- * @param bool Generate a NOT IN clause instead
- * @return Database_Core This Database object.
- */
- public function in($field, $values, $not = FALSE)
- {
- if (is_array($values))
+ if ($this->config['escape'])
{
- $escaped_values = array();
- foreach ($values as $v)
- {
- if (is_numeric($v))
- {
- $escaped_values[] = $v;
- }
- else
- {
- $escaped_values[] = "'".$this->driver->escape_str($v)."'";
- }
- }
- $values = implode(",", $escaped_values);
+ $table = $this->quote.$table.$this->quote;
}
- $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
- $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
-
- return $this;
+ return $table;
}
/**
- * Adds a "NOT IN" condition to the where clause
+ * Quotes column or table.column, adding the table prefix if necessary
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")')
*
- * @param string Name of the column being examined
- * @param mixed An array or string to match against
- * @return Database_Core This Database object.
+ * @param string|array column name or array('u' => 'COUNT("*")')
+ * @param string column alias
+ * @return string
*/
- public function notin($field, $values)
+ public function quote_column($column, $alias = NULL)
{
- return $this->in($field, $values, TRUE);
- }
+ if ($column === '*')
+ return $column;
- /**
- * Compiles a merge string and runs the query.
- *
- * @param string table name
- * @param array array of key/value pairs to merge
- * @return Database_Result Query result
- */
- public function merge($table = '', $set = NULL)
- {
- if ( ! is_null($set))
+ if (is_array($column))
{
- $this->set($set);
+ list($alias, $column) = each($column);
}
- if ($this->set == NULL)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
- else
+ if ($column instanceof Database_Expression)
{
- $table = $this->config['table_prefix'].$table;
- }
-
- $sql = $this->driver->merge($table, array_keys($this->set), array_values($this->set));
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
- $this->reset_write();
- return $this->query($sql);
- }
+ return $column.' AS '.$alias;
+ }
- /**
- * Compiles an update string and runs the query.
- *
- * @param string table name
- * @param array associative array of update values
- * @param array where clause
- * @return Database_Result Query result
- */
- public function update($table = '', $set = NULL, $where = NULL)
- {
- if ( is_array($set))
- {
- $this->set($set);
+ return (string) $column;
}
- if ( ! is_null($where))
+ if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE)
{
- $this->where($where);
+ if (strpos($column, '"') !== FALSE)
+ {
+ // Find "table.column" and replace them with "[prefix]table.column"
+ $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column);
+ }
+ else
+ {
+ // Attach table prefix if table.column format
+ $column = $this->config['table_prefix'].$column;
+ }
}
- if ($this->set == FALSE)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
- else
+ if ($this->config['escape'])
{
- $table = $this->config['table_prefix'].$table;
- }
-
- $sql = $this->driver->update($table, $this->set, $this->where);
+ if (strpos($column, '"') === FALSE)
+ {
+ // Quote the column
+ $column = $this->quote.$column.$this->quote;
+ }
+ elseif ($this->quote !== '"')
+ {
+ // Replace double quotes
+ $column = str_replace('"', $this->quote, $column);
+ }
- $this->reset_write();
- return $this->query($sql);
- }
+ // Replace . with "."
+ $column = str_replace('.', $this->quote.'.'.$this->quote, $column);
- /**
- * Compiles a delete string and runs the query.
- *
- * @param string table name
- * @param array where clause
- * @return Database_Result Query result
- */
- public function delete($table = '', $where = NULL)
- {
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
+ // Unescape any asterisks
+ $column = str_replace($this->quote.'*'.$this->quote, '*', $column);
- $table = $this->from[0];
- }
- else
- {
- $table = $this->config['table_prefix'].$table;
- }
+ if ($alias)
+ {
+ // Quote the alias
+ return $column.' AS '.$this->quote.$alias.$this->quote;
+ }
- if (! is_null($where))
- {
- $this->where($where);
+ return $column;
}
- if (count($this->where) < 1)
- throw new Kohana_Database_Exception('database.must_use_where');
+ // Strip double quotes
+ $column = str_replace('"', '', $column);
- $sql = $this->driver->delete($table, $this->where);
+ if ($alias)
+ return $column.' AS '.$alias;
- $this->reset_write();
- return $this->query($sql);
+ return $column;
}
/**
- * Returns the last query run.
+ * Get the table prefix
*
- * @return string SQL
+ * @param string Optional new table prefix to set
+ * @return string
*/
- public function last_query()
+ public function table_prefix($new_prefix = NULL)
{
- return $this->last_query;
- }
+ $prefix = $this->config['table_prefix'];
- /**
- * Count query records.
- *
- * @param string table name
- * @param array where clause
- * @return integer
- */
- public function count_records($table = FALSE, $where = NULL)
- {
- if (count($this->from) < 1)
+ if ($new_prefix !== NULL)
{
- if ($table == FALSE)
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $this->from($table);
+ // Set a new prefix
+ $this->config['table_prefix'] = $new_prefix;
}
- if ($where !== NULL)
- {
- $this->where($where);
- }
-
- $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
-
- $query = $query->current();
-
- if ( ! $query)
- return 0;
- else
- return (int) $query->records_found;
- }
-
- /**
- * Resets all private select variables.
- *
- * @return void
- */
- protected function reset_select()
- {
- $this->select = array();
- $this->from = array();
- $this->join = array();
- $this->where = array();
- $this->orderby = array();
- $this->groupby = array();
- $this->having = array();
- $this->distinct = FALSE;
- $this->limit = FALSE;
- $this->offset = FALSE;
+ return $prefix;
}
/**
- * Resets all private insert and update variables.
- *
- * @return void
- */
- protected function reset_write()
- {
- $this->set = array();
- $this->from = array();
- $this->where = array();
- }
-
- /**
- * Lists all the tables in the current database.
+ * Fetches SQL type information about a field, in a generic format.
*
+ * @param string field datatype
* @return array
*/
- public function list_tables()
- {
- $this->link or $this->connect();
-
- return $this->driver->list_tables();
- }
-
- /**
- * See if a table exists in the database.
- *
- * @param string table name
- * @param boolean True to attach table prefix
- * @return boolean
- */
- public function table_exists($table_name, $prefix = TRUE)
+ protected function sql_type($str)
{
- if ($prefix)
- return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
- else
- return in_array($table_name, $this->list_tables());
- }
+ static $sql_types;
- /**
- * Combine a SQL statement with the bind values. Used for safe queries.
- *
- * @param string query to bind to the values
- * @param array array of values to bind to the query
- * @return string
- */
- public function compile_binds($sql, $binds)
- {
- foreach ((array) $binds as $val)
+ if ($sql_types === NULL)
{
- // If the SQL contains no more bind marks ("?"), we're done.
- if (($next_bind_pos = strpos($sql, '?')) === FALSE)
- break;
-
- // Properly escape the bind value.
- $val = $this->driver->escape($val);
-
- // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
- $val = str_replace('?', '{%B%}', $val);
-
- // Replace the first bind mark ("?") with its corresponding value.
- $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
+ // Load SQL data types
+ $sql_types = Kohana::config('sql_types');
}
- // Restore placeholders.
- return str_replace('{%B%}', '?', $sql);
- }
+ $str = trim($str);
- /**
- * Get the field data for a database table, along with the field's attributes.
- *
- * @param string table name
- * @return array
- */
- public function field_data($table = '')
- {
- $this->link or $this->connect();
-
- return $this->driver->field_data($this->config['table_prefix'].$table);
- }
-
- /**
- * Get the field data for a database table, along with the field's attributes.
- *
- * @param string table name
- * @return array
- */
- public function list_fields($table = '')
- {
- $this->link or $this->connect();
-
- return $this->driver->list_fields($this->config['table_prefix'].$table);
- }
-
- /**
- * Escapes a value for a query.
- *
- * @param mixed value to escape
- * @return string
- */
- public function escape($value)
- {
- return $this->driver->escape($value);
- }
-
- /**
- * Escapes a string for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_str($str)
- {
- return $this->driver->escape_str($str);
- }
-
- /**
- * Escapes a table name for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_table($table)
- {
- return $this->driver->escape_table($table);
- }
-
- /**
- * Escapes a column name for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_column($table)
- {
- return $this->driver->escape_column($table);
- }
+ if (($open = strpos($str, '(')) !== FALSE)
+ {
+ // Closing bracket
+ $close = strpos($str, ')', $open);
- /**
- * Returns table prefix of current configuration.
- *
- * @return string
- */
- public function table_prefix()
- {
- return $this->config['table_prefix'];
- }
+ // Length without brackets
+ $length = substr($str, $open + 1, $close - 1 - $open);
- /**
- * Clears the query cache.
- *
- * @param string|TRUE clear cache by SQL statement or TRUE for last query
- * @return Database_Core This Database object.
- */
- public function clear_cache($sql = NULL)
- {
- if ($sql === TRUE)
- {
- $this->driver->clear_cache($this->last_query);
- }
- elseif (is_string($sql))
- {
- $this->driver->clear_cache($sql);
+ // Type without the length
+ $type = substr($str, 0, $open).substr($str, $close + 1);
}
else
{
- $this->driver->clear_cache();
+ // No length
+ $type = $str;
}
- return $this;
- }
+ if (empty($sql_types[$type]))
+ throw new Database_Exception('Undefined field type :type', array(':type' => $str));
- /**
- * Pushes existing query space onto the query stack. Use push
- * and pop to prevent queries from clashing before they are
- * executed
- *
- * @return Database_Core This Databaes object
- */
- public function push()
- {
- array_push($this->query_history, array(
- $this->select,
- $this->from,
- $this->join,
- $this->where,
- $this->orderby,
- $this->order,
- $this->groupby,
- $this->having,
- $this->distinct,
- $this->limit,
- $this->offset
- ));
-
- $this->reset_select();
-
- return $this;
- }
+ // Fetch the field definition
+ $field = $sql_types[$type];
- /**
- * Pops from query stack into the current query space.
- *
- * @return Database_Core This Databaes object
- */
- public function pop()
- {
- if (count($this->query_history) == 0)
- {
- // No history
- return $this;
- }
-
- list(
- $this->select,
- $this->from,
- $this->join,
- $this->where,
- $this->orderby,
- $this->order,
- $this->groupby,
- $this->having,
- $this->distinct,
- $this->limit,
- $this->offset
- ) = array_pop($this->query_history);
-
- return $this;
- }
+ $field['sql_type'] = $type;
- /**
- * Count the number of records in the last query, without LIMIT or OFFSET applied.
- *
- * @return integer
- */
- public function count_last_query()
- {
- if ($sql = $this->last_query())
+ if (isset($length))
{
- if (stripos($sql, 'LIMIT') !== FALSE)
- {
- // Remove LIMIT from the SQL
- $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
- }
-
- if (stripos($sql, 'OFFSET') !== FALSE)
- {
- // Remove OFFSET from the SQL
- $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
- }
-
- // Get the total rows from the last query executed
- $result = $this->query
- (
- 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '.
- 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results')
- );
-
- // Return the total number of rows from the query
- return (int) $result->current()->total_rows;
+ // Add the length to the field info
+ $field['length'] = $length;
}
- return FALSE;
+ return $field;
}
-} // End Database Class
-
-
-/**
- * Sets the code for a Database exception.
- */
-class Kohana_Database_Exception extends Kohana_Exception {
-
- protected $code = E_DATABASE_ERROR;
-
-} // End Kohana Database Exception
+} // End Database