Upgrade to KH 3.1.3.1

This commit is contained in:
Deon George
2011-05-13 16:00:25 +10:00
parent 8013aadc4c
commit 6d256839fc
675 changed files with 22771 additions and 24111 deletions

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Config_Database extends Kohana_Config_Database {}

View File

@@ -15,7 +15,7 @@
* @copyright (c) 2009 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Config_Database extends Kohana_Config_Reader {
class Kohana_Config_Database extends Config_Reader {
protected $_database_instance = 'default';

View File

@@ -1,8 +1,14 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database connection wrapper. All database object instances are referenced
* by a name. Queries are typically handled by [Database_Query], rather than
* using the database object directly.
* Database connection wrapper/helper.
*
* You may get a database instance using `Database::instance('name')` where
* name is the [config](database/config) group.
*
* This class provides connection instance management via Database Drivers, as
* well as quoting, escaping and other related functions. Querys are done using
* [Database_Query] and [Database_Query_Builder] objects, which can be easily
* created using the [DB] helper class.
*
* @package Kohana/Database
* @category Base
@@ -152,12 +158,18 @@ abstract class Kohana_Database {
/**
* Disconnect from the database. This is called automatically by [Database::__destruct].
* Clears the database instance from [Database::$instances].
*
* $db->disconnect();
*
* @return boolean
*/
abstract public function disconnect();
public function disconnect()
{
unset(Database::$instances[$this->_instance]);
return TRUE;
}
/**
* Set the connection character set. This is called automatically by [Database::connect].
@@ -181,58 +193,56 @@ abstract class Kohana_Database {
*
* @param integer Database::SELECT, Database::INSERT, etc
* @param string SQL query
* @param mixed result object class, TRUE for stdClass, FALSE for assoc array
* @param mixed result object class string, TRUE for stdClass, FALSE for assoc array
* @param array object construct parameters for result class
* @return object Database_Result for SELECT queries
* @return array list (insert id, row count) for INSERT queries
* @return integer number of affected rows for all other queries
*/
abstract public function query($type, $sql, $as_object);
abstract public function query($type, $sql, $as_object = FALSE, array $params = NULL);
/**
* Count the number of records in the last query, without LIMIT or OFFSET applied.
* Start a SQL transaction
*
* // Get the total number of records that match the last query
* $count = $db->count_last_query();
* // Start the transactions
* $db->begin();
*
* @return integer
* try {
* DB::insert('users')->values($user1)...
* DB::insert('users')->values($user2)...
* // Insert successful commit the changes
* $db->commit();
* }
* catch (Database_Exception $e)
* {
* // Insert failed. Rolling back changes...
* $db->rollback();
* }
*
* @param string transaction mode
* @return boolean
*/
public function count_last_query()
{
if ($sql = $this->last_query)
{
$sql = trim($sql);
if (stripos($sql, 'SELECT') !== 0)
{
return FALSE;
}
abstract public function begin($mode = NULL);
if (stripos($sql, 'LIMIT') !== FALSE)
{
// Remove LIMIT from the SQL
$sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
}
/**
* Commit the current transaction
*
* // Commit the database changes
* $db->commit();
*
* @return boolean
*/
abstract public function commit();
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
(
Database::SELECT,
'SELECT COUNT(*) AS '.$this->quote_identifier('total_rows').' '.
'FROM ('.$sql.') AS '.$this->quote_table('counted_results'),
TRUE
);
// Return the total number of rows from the query
return (int) $result->current()->total_rows;
}
return FALSE;
}
/**
* Abort the current transaction
*
* // Undo the changes
* $db->rollback();
*
* @return boolean
*/
abstract public function rollback();
/**
* Count the number of records in a table.
@@ -246,7 +256,7 @@ abstract class Kohana_Database {
public function count_records($table)
{
// Quote the table name
$table = $this->quote_identifier($table);
$table = $this->quote_table($table);
return $this->query(Database::SELECT, 'SELECT COUNT(*) AS total_row_count FROM '.$table, FALSE)
->get('total_row_count');
@@ -347,11 +357,15 @@ abstract class Kohana_Database {
* // Get all name-related columns
* $columns = $db->list_columns('users', '%name%');
*
* // Get the columns from a table that doesn't use the table prefix
* $columns = $db->list_columns('users', NULL, FALSE);
*
* @param string table to get columns from
* @param string column to search for
* @param boolean whether to add the table prefix automatically or not
* @return array
*/
abstract public function list_columns($table, $like = NULL);
abstract public function list_columns($table, $like = NULL, $add_prefix = TRUE);
/**
* Extracts the text between parentheses, if any.
@@ -439,7 +453,7 @@ abstract class Kohana_Database {
else
{
// Convert the object to a string
return $this->quote((string) $value);
return $this->quote( (string) $value);
}
}
elseif (is_array($value))
@@ -459,6 +473,90 @@ abstract class Kohana_Database {
return $this->escape($value);
}
/**
* Quote a database column name and add the table prefix if needed.
*
* $column = $db->quote_column($column);
*
* You can also use SQL methods within identifiers.
*
* // The value of "column" will be quoted
* $column = $db->quote_column('COUNT("column")');
*
* @param mixed column name or array(column, alias)
* @return string
* @uses Database::quote_identifier
* @uses Database::table_prefix
*/
public function quote_column($column)
{
if (is_array($column))
{
list($column, $alias) = $column;
}
if ($column instanceof Database_Query)
{
// Create a sub-query
$column = '('.$column->compile($this).')';
}
elseif ($column instanceof Database_Expression)
{
// Use a raw expression
$column = $column->value();
}
else
{
// Convert to a string
$column = (string) $column;
if ($column === '*')
{
return $column;
}
elseif (strpos($column, '"') !== FALSE)
{
// Quote the column in FUNC("column") identifiers
$column = preg_replace('/"(.+?)"/e', '$this->quote_column("$1")', $column);
}
elseif (strpos($column, '.') !== FALSE)
{
$parts = explode('.', $column);
if ($prefix = $this->table_prefix())
{
// Get the offset of the table name, 2nd-to-last part
$offset = count($parts) - 2;
// Add the table prefix to the table name
$parts[$offset] = $prefix.$parts[$offset];
}
foreach ($parts as & $part)
{
if ($part !== '*')
{
// Quote each of the parts
$part = $this->_identifier.$part.$this->_identifier;
}
}
$column = implode('.', $parts);
}
else
{
$column = $this->_identifier.$column.$this->_identifier;
}
}
if (isset($alias))
{
$column .= ' AS '.$this->_identifier.$alias.$this->_identifier;
}
return $column;
}
/**
* Quote a database table name and adds the table prefix if needed.
*
@@ -469,40 +567,67 @@ abstract class Kohana_Database {
* @uses Database::quote_identifier
* @uses Database::table_prefix
*/
public function quote_table($value)
public function quote_table($table)
{
// Assign the table by reference from the value
if (is_array($value))
if (is_array($table))
{
$table =& $value[0];
list($table, $alias) = $table;
}
// Attach table prefix to alias
$value[1] = $this->table_prefix().$value[1];
if ($table instanceof Database_Query)
{
// Create a sub-query
$table = '('.$table->compile($this).')';
}
elseif ($table instanceof Database_Expression)
{
// Use a raw expression
$table = $table->value();
}
else
{
$table =& $value;
// Convert to a string
$table = (string) $table;
if (strpos($table, '.') !== FALSE)
{
$parts = explode('.', $table);
if ($prefix = $this->table_prefix())
{
// Get the offset of the table name, last part
$offset = count($parts) - 1;
// Add the table prefix to the table name
$parts[$offset] = $prefix.$parts[$offset];
}
foreach ($parts as & $part)
{
// Quote each of the parts
$part = $this->_identifier.$part.$this->_identifier;
}
$table = implode('.', $parts);
}
else
{
// Add the table prefix
$table = $this->_identifier.$this->table_prefix().$table.$this->_identifier;
}
}
if (is_string($table) AND strpos($table, '.') === FALSE)
if (isset($alias))
{
// Add the table prefix for tables
$table = $this->table_prefix().$table;
// Attach table prefix to alias
$table .= ' AS '.$this->_identifier.$this->table_prefix().$alias.$this->_identifier;
}
return $this->quote_identifier($value);
return $table;
}
/**
* Quote a database identifier, such as a column name. Adds the
* table prefix to the identifier if a table name is present.
*
* $column = $db->quote_identifier($column);
*
* You can also use SQL methods within identifiers.
*
* // The value of "column" will be quoted
* $column = $db->quote_identifier('COUNT("column")');
* Quote a database identifier
*
* Objects passed to this function will be converted to strings.
* [Database_Expression] objects will use the value of the expression.
@@ -511,67 +636,53 @@ abstract class Kohana_Database {
*
* @param mixed any identifier
* @return string
* @uses Database::table_prefix
*/
public function quote_identifier($value)
{
if ($value === '*')
if (is_array($value))
{
return $value;
}
elseif (is_object($value))
{
if ($value instanceof Database_Query)
{
// Create a sub-query
return '('.$value->compile($this).')';
}
elseif ($value instanceof Database_Expression)
{
// Use a raw expression
return $value->value();
}
else
{
// Convert the object to a string
return $this->quote_identifier((string) $value);
}
}
elseif (is_array($value))
{
// Separate the column and alias
list ($value, $alias) = $value;
return $this->quote_identifier($value).' AS '.$this->quote_identifier($alias);
list($value, $alias) = $value;
}
if (strpos($value, '"') !== FALSE)
if ($value instanceof Database_Query)
{
// Quote the column in FUNC("ident") identifiers
return preg_replace('/"(.+?)"/e', '$this->quote_identifier("$1")', $value);
// Create a sub-query
$value = '('.$value->compile($this).')';
}
elseif (strpos($value, '.') !== FALSE)
elseif ($value instanceof Database_Expression)
{
// Split the identifier into the individual parts
$parts = explode('.', $value);
if ($prefix = $this->table_prefix())
{
// Get the offset of the table name, 2nd-to-last part
// This works for databases that can have 3 identifiers (Postgre)
$offset = count($parts) - 2;
// Add the table prefix to the table name
$parts[$offset] = $prefix.$parts[$offset];
}
// Quote each of the parts
return implode('.', array_map(array($this, __FUNCTION__), $parts));
// Use a raw expression
$value = $value->value();
}
else
{
return $this->_identifier.$value.$this->_identifier;
// Convert to a string
$value = (string) $value;
if (strpos($value, '.') !== FALSE)
{
$parts = explode('.', $value);
foreach ($parts as & $part)
{
// Quote each of the parts
$part = $this->_identifier.$part.$this->_identifier;
}
$value = implode('.', $parts);
}
else
{
$value = $this->_identifier.$value.$this->_identifier;
}
}
if (isset($alias))
{
$value .= ' AS '.$this->_identifier.$alias.$this->_identifier;
}
return $value;
}
/**

View File

@@ -8,6 +8,8 @@
* // SELECT CONCAT(first_name, last_name) AS full_name
* $query = DB::select(array(DB::expr('CONCAT(first_name, last_name)'), 'full_name')));
*
* More examples are available on the [Query Builder](database/query/builder#database-expressions) page
*
* @package Kohana/Database
* @category Base
* @author Kohana Team

View File

@@ -64,9 +64,8 @@ class Kohana_Database_MySQL extends Database {
// No connection exists
$this->_connection = NULL;
throw new Database_Exception(':error', array(
':error' => mysql_error(),
),
throw new Database_Exception(':error',
array(':error' => mysql_error()),
mysql_errno());
}
@@ -114,6 +113,9 @@ class Kohana_Database_MySQL extends Database {
{
// Clear the connection
$this->_connection = NULL;
// Clear the instance
parent::disconnect();
}
}
}
@@ -150,7 +152,7 @@ class Kohana_Database_MySQL extends Database {
}
}
public function query($type, $sql, $as_object)
public function query($type, $sql, $as_object = FALSE, array $params = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
@@ -192,7 +194,7 @@ class Kohana_Database_MySQL extends Database {
if ($type === Database::SELECT)
{
// Return an iterator of results
return new Database_MySQL_Result($result, $sql, $as_object);
return new Database_MySQL_Result($result, $sql, $as_object, $params);
}
elseif ($type === Database::INSERT)
{
@@ -256,6 +258,57 @@ class Kohana_Database_MySQL extends Database {
return parent::datatype($type);
}
/**
* Start a SQL transaction
*
* @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
*
* @param string Isolation level
* @return boolean
*/
public function begin($mode = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
if ($mode AND ! mysql_query("SET TRANSACTION ISOLATION LEVEL $mode", $this->_connection))
{
throw new Database_Exception(':error',
array(':error' => mysql_error($this->_connection)),
mysql_errno($this->_connection));
}
return (bool) mysql_query('START TRANSACTION', $this->_connection);
}
/**
* Commit a SQL transaction
*
* @param string Isolation level
* @return boolean
*/
public function commit()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return (bool) mysql_query('COMMIT', $this->_connection);
}
/**
* Rollback a SQL transaction
*
* @param string Isolation level
* @return boolean
*/
public function rollback()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return (bool) mysql_query('ROLLBACK', $this->_connection);
}
public function list_tables($like = NULL)
{
if (is_string($like))
@@ -278,10 +331,10 @@ class Kohana_Database_MySQL extends Database {
return $tables;
}
public function list_columns($table, $like = NULL)
public function list_columns($table, $like = NULL, $add_prefix = TRUE)
{
// Quote the table name
$table = $this->quote_table($table);
$table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table;
if (is_string($like))
{
@@ -330,7 +383,6 @@ class Kohana_Database_MySQL extends Database {
case 'varbinary':
$column['character_maximum_length'] = $length;
break;
case 'char':
case 'varchar':
$column['character_maximum_length'] = $length;
@@ -340,7 +392,6 @@ class Kohana_Database_MySQL extends Database {
case 'longtext':
$column['collation_name'] = $row['Collation'];
break;
case 'enum':
case 'set':
$column['collation_name'] = $row['Collation'];
@@ -367,11 +418,11 @@ class Kohana_Database_MySQL extends Database {
// Make sure the database is connected
$this->_connection or $this->connect();
if (($value = mysql_real_escape_string((string) $value, $this->_connection)) === FALSE)
if (($value = mysql_real_escape_string( (string) $value, $this->_connection)) === FALSE)
{
throw new Database_Exception(':error',
array(':error' => mysql_errno($this->_connection)),
mysql_error($this->_connection));
array(':error' => mysql_error($this->_connection)),
mysql_errno($this->_connection));
}
// SQL standard is to use single-quotes for all values

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* MySQL database result.
* MySQL database result. See [Results](/database/results) for usage and examples.
*
* @package Kohana/Database
* @category Query/Result
@@ -12,9 +12,9 @@ class Kohana_Database_MySQL_Result extends Database_Result {
protected $_internal_row = 0;
public function __construct($result, $sql, $as_object)
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL)
{
parent::__construct($result, $sql, $as_object);
parent::__construct($result, $sql, $as_object, $params);
// Find the number of rows in the result
$this->_total_rows = mysql_num_rows($result);
@@ -46,7 +46,7 @@ class Kohana_Database_MySQL_Result extends Database_Result {
public function current()
{
if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row))
return FALSE;
return NULL;
// Increment internal row for optimization assuming rows are fetched in order
$this->_internal_row++;
@@ -59,7 +59,7 @@ class Kohana_Database_MySQL_Result extends Database_Result {
elseif (is_string($this->_as_object))
{
// Return an object of given class name
return mysql_fetch_object($this->_result, $this->_as_object);
return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params);
}
else
{

View File

@@ -56,11 +56,9 @@ class Kohana_Database_PDO extends Database {
}
catch (PDOException $e)
{
throw new Database_Exception(':error', array(
':error' => $e->getMessage(),
),
$e->getCode(),
$e);
throw new Database_Exception(':error',
array(':error' => $e->getMessage()),
$e->getCode());
}
if ( ! empty($this->_config['charset']))
@@ -75,7 +73,7 @@ class Kohana_Database_PDO extends Database {
// Destroy the PDO object
$this->_connection = NULL;
return TRUE;
return parent::disconnect();
}
public function set_charset($charset)
@@ -87,7 +85,7 @@ class Kohana_Database_PDO extends Database {
$this->_connection->exec('SET NAMES '.$this->quote($charset));
}
public function query($type, $sql, $as_object)
public function query($type, $sql, $as_object = FALSE, array $params = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
@@ -111,12 +109,12 @@ class Kohana_Database_PDO extends Database {
}
// Convert the exception in a database exception
throw new Database_Exception(':error [ :query ]', array(
throw new Database_Exception(':error [ :query ]',
array(
':error' => $e->getMessage(),
':query' => $sql
),
$e->getCode(),
$e);
$e->getCode());
}
if (isset($benchmark))
@@ -136,7 +134,7 @@ class Kohana_Database_PDO extends Database {
}
elseif (is_string($as_object))
{
$result->setFetchMode(PDO::FETCH_CLASS, $as_object);
$result->setFetchMode(PDO::FETCH_CLASS, $as_object, $params);
}
else
{
@@ -146,7 +144,7 @@ class Kohana_Database_PDO extends Database {
$result = $result->fetchAll();
// Return an iterator of results
return new Database_Result_Cached($result, $sql, $as_object);
return new Database_Result_Cached($result, $sql, $as_object, $params);
}
elseif ($type === Database::INSERT)
{
@@ -163,13 +161,37 @@ class Kohana_Database_PDO extends Database {
}
}
public function begin($mode = NULL)
{
// Make sure the database is connected
$this->_connection or $this->connect();
return $this->_connection->beginTransaction();
}
public function commit()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return $this->_connection->commit();
}
public function rollback()
{
// Make sure the database is connected
$this->_connection or $this->connect();
return $this->_connection->rollBack();
}
public function list_tables($like = NULL)
{
throw new Kohana_Exception('Database method :method is not supported by :class',
array(':method' => __FUNCTION__, ':class' => __CLASS__));
}
public function list_columns($table, $like = NULL)
public function list_columns($table, $like = NULL, $add_prefix = TRUE)
{
throw new Kohana_Exception('Database method :method is not supported by :class',
array(':method' => __FUNCTION__, ':class' => __CLASS__));

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query wrapper.
* Database query wrapper. See [Prepared Statements](database/query/prepared) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -14,7 +14,7 @@ class Kohana_Database_Query {
protected $_type;
// Cache lifetime
protected $_lifetime;
protected $_lifetime = NULL;
// SQL statement
protected $_sql;
@@ -25,6 +25,9 @@ class Kohana_Database_Query {
// Return results as associative arrays or objects
protected $_as_object = FALSE;
// Parameters for __construct when using object results
protected $_object_params = array();
/**
* Creates a new SQL query of the specified type.
*
@@ -52,7 +55,7 @@ class Kohana_Database_Query {
}
catch (Exception $e)
{
return Kohana::exception_text($e);
return Kohana_Exception::text($e);
}
}
@@ -69,11 +72,18 @@ class Kohana_Database_Query {
/**
* Enables the query to be cached for a specified amount of time.
*
* @param integer number of seconds to cache or null for default
* @param integer number of seconds to cache
* @return $this
* @uses Kohana::$cache_life
*/
public function cached($lifetime = NULL)
{
if ($lifetime === NULL)
{
// Use the global setting
$lifetime = Kohana::$cache_life;
}
$this->_lifetime = $lifetime;
return $this;
@@ -88,6 +98,8 @@ class Kohana_Database_Query {
{
$this->_as_object = FALSE;
$this->_object_params = array();
return $this;
}
@@ -97,10 +109,16 @@ class Kohana_Database_Query {
* @param string classname or TRUE for stdClass
* @return $this
*/
public function as_object($class = TRUE)
public function as_object($class = TRUE, array $params = NULL)
{
$this->_as_object = $class;
if ($params)
{
// Add object parameters
$this->_object_params = $params;
}
return $this;
}
@@ -191,7 +209,7 @@ class Kohana_Database_Query {
// Compile the SQL query
$sql = $this->compile($db);
if ( ! empty($this->_lifetime) AND $this->_type === Database::SELECT)
if ($this->_lifetime !== NULL AND $this->_type === Database::SELECT)
{
// Set the cache key based on the database instance name and SQL
$cache_key = 'Database::query("'.$db.'", "'.$sql.'")';
@@ -199,12 +217,12 @@ class Kohana_Database_Query {
if ($result = Kohana::cache($cache_key, NULL, $this->_lifetime))
{
// Return a cached result
return new Database_Result_Cached($result, $sql, $this->_as_object);
return new Database_Result_Cached($result, $sql, $this->_as_object, $this->_object_params);
}
}
// Execute the query
$result = $db->query($this->_type, $sql, $this->_as_object);
$result = $db->query($this->_type, $sql, $this->_as_object, $this->_object_params);
if (isset($cache_key))
{

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder.
* Database query builder. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -95,35 +95,35 @@ abstract class Kohana_Database_Query_Builder extends Database_Query {
// BETWEEN always has exactly two arguments
list($min, $max) = $value;
if (is_string($min) AND array_key_exists($min, $this->_parameters))
if ((is_string($min) AND array_key_exists($min, $this->_parameters)) === FALSE)
{
// Set the parameter as the minimum
$min = $this->_parameters[$min];
// Quote the value, it is not a parameter
$min = $db->quote($min);
}
if (is_string($max) AND array_key_exists($max, $this->_parameters))
if ((is_string($max) AND array_key_exists($max, $this->_parameters)) === FALSE)
{
// Set the parameter as the maximum
$max = $this->_parameters[$max];
// Quote the value, it is not a parameter
$max = $db->quote($max);
}
// Quote the min and max value
$value = $db->quote($min).' AND '.$db->quote($max);
$value = $min.' AND '.$max;
}
else
elseif ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE)
{
if (is_string($value) AND array_key_exists($value, $this->_parameters))
{
// Set the parameter as the value
$value = $this->_parameters[$value];
}
// Quote the entire value normally
// Quote the value, it is not a parameter
$value = $db->quote($value);
}
if ($column)
{
// Apply proper quoting to the column
$column = $db->quote_column($column);
}
// Append the statement to the query
$sql .= $db->quote_identifier($column).' '.$op.' '.$value;
$sql .= trim($column.' '.$op.' '.$value);
}
$last_condition = $condition;
@@ -149,15 +149,15 @@ abstract class Kohana_Database_Query_Builder extends Database_Query {
list ($column, $value) = $group;
// Quote the column name
$column = $db->quote_identifier($column);
$column = $db->quote_column($column);
if (is_string($value) AND array_key_exists($value, $this->_parameters))
if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE)
{
// Use the parameter value
$value = $this->_parameters[$value];
// Quote the value, it is not a parameter
$value = $db->quote($value);
}
$set[$column] = $column.' = '.$db->quote($value);
$set[$column] = $column.' = '.$value;
}
return implode(', ', $set);
@@ -177,13 +177,19 @@ abstract class Kohana_Database_Query_Builder extends Database_Query {
{
list ($column, $direction) = $group;
if ( ! empty($direction))
if ($direction)
{
// Make the direction uppercase
$direction = ' '.strtoupper($direction);
$direction = strtoupper($direction);
}
$sort[] = $db->quote_identifier($column).$direction;
if ($column)
{
// Quote the column, if it has a value
$column = $db->quote_column($column);
}
$sort[] = trim($column.' '.$direction);
}
return 'ORDER BY '.implode(', ', $sort);

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for DELETE statements.
* Database query builder for DELETE statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -73,7 +73,9 @@ class Kohana_Database_Query_Builder_Delete extends Database_Query_Builder_Where
$query .= ' LIMIT '.$this->_limit;
}
return $query;
$this->_sql = $query;
return parent::compile($db);
}
public function reset()
@@ -83,6 +85,8 @@ class Kohana_Database_Query_Builder_Delete extends Database_Query_Builder_Where
$this->_parameters = array();
$this->_sql = NULL;
return $this;
}

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for INSERT statements.
* Database query builder for INSERT statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -122,7 +122,7 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
$query = 'INSERT INTO '.$db->quote_table($this->_table);
// Add the column names
$query .= ' ('.implode(', ', array_map(array($db, 'quote_identifier'), $this->_columns)).') ';
$query .= ' ('.implode(', ', array_map(array($db, 'quote_column'), $this->_columns)).') ';
if (is_array($this->_values))
{
@@ -132,16 +132,16 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
$groups = array();
foreach ($this->_values as $group)
{
foreach ($group as $i => $value)
foreach ($group as $offset => $value)
{
if (is_string($value) AND isset($this->_parameters[$value]))
if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE)
{
// Use the parameter value
$group[$i] = $this->_parameters[$value];
// Quote the value, it is not a parameter
$group[$offset] = $db->quote($value);
}
}
$groups[] = '('.implode(', ', array_map($quote, $group)).')';
$groups[] = '('.implode(', ', $group).')';
}
// Add the values
@@ -153,7 +153,9 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
$query .= (string) $this->_values;
}
return $query;
$this->_sql = $query;
return parent::compile($db);;
}
public function reset()
@@ -165,6 +167,8 @@ class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
$this->_parameters = array();
$this->_sql = NULL;
return $this;
}

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for JOIN statements.
* Database query builder for JOIN statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -19,6 +19,9 @@ class Kohana_Database_Query_Builder_Join extends Database_Query_Builder {
// ON ...
protected $_on = array();
// USING ...
protected $_using = array();
/**
* Creates a new JOIN statement for a table. Optionally, the type of JOIN
* can be specified as the second parameter.
@@ -49,11 +52,37 @@ class Kohana_Database_Query_Builder_Join extends Database_Query_Builder {
*/
public function on($c1, $op, $c2)
{
if ( ! empty($this->_using))
{
throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...');
}
$this->_on[] = array($c1, $op, $c2);
return $this;
}
/**
* Adds a new condition for joining.
*
* @param string column name
* @param ...
* @return $this
*/
public function using($columns)
{
if ( ! empty($this->_on))
{
throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...');
}
$columns = func_get_args();
$this->_using = array_merge($this->_using, $columns);
return $this;
}
/**
* Compile the SQL partial for a JOIN statement and return it.
*
@@ -72,27 +101,35 @@ class Kohana_Database_Query_Builder_Join extends Database_Query_Builder {
}
// Quote the table name that is being joined
$sql .= ' '.$db->quote_table($this->_table).' ON ';
$sql .= ' '.$db->quote_table($this->_table);
$conditions = array();
foreach ($this->_on as $condition)
if ( ! empty($this->_using))
{
// Split the condition
list($c1, $op, $c2) = $condition;
if ($op)
// Quote and concat the columns
$sql .= ' USING ('.implode(', ', array_map(array($db, 'quote_column'), $this->_using)).')';
}
else
{
$conditions = array();
foreach ($this->_on as $condition)
{
// Make the operator uppercase and spaced
$op = ' '.strtoupper($op);
// Split the condition
list($c1, $op, $c2) = $condition;
if ($op)
{
// Make the operator uppercase and spaced
$op = ' '.strtoupper($op);
}
// Quote each of the columns used for the condition
$conditions[] = $db->quote_column($c1).$op.' '.$db->quote_column($c2);
}
// Quote each of the identifiers used for the condition
$conditions[] = $db->quote_identifier($c1).$op.' '.$db->quote_identifier($c2);
// Concat the conditions "... AND ..."
$sql .= ' ON ('.implode(' AND ', $conditions).')';
}
// Concat the conditions "... AND ..."
$sql .= '('.implode(' AND ', $conditions).')';
return $sql;
}

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for SELECT statements.
* Database query builder for SELECT statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -31,6 +31,9 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
// OFFSET ...
protected $_offset = NULL;
// UNION ...
protected $_union = array();
// The last JOIN statement created
protected $_last_join;
@@ -76,10 +79,6 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
{
$columns = func_get_args();
// Ignore our default select of .* if we already have some selected columns.
if (count($columns) == 1 AND is_string($columns[0]) AND preg_match('/\.\*$/',$columns[0]) AND $this->_select)
return $this;
$this->_select = array_merge($this->_select, $columns);
return $this;
@@ -143,6 +142,22 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
return $this;
}
/**
* Adds "USING ..." conditions for the last created JOIN statement.
*
* @param string column name
* @param ...
* @return $this
*/
public function using($columns)
{
$columns = func_get_args();
call_user_func_array(array($this->_last_join, 'using'), $columns);
return $this;
}
/**
* Creates a "GROUP BY ..." filter.
*
@@ -270,6 +285,26 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
return $this;
}
/**
* Adds an other UNION clause.
*
* @param mixed $select if string, it must be the name of a table. Else
* must be an instance of Database_Query_Builder_Select
* @param boolean $all decides if it's an UNION or UNION ALL clause
* @return $this
*/
public function union($select, $all = TRUE)
{
if (is_string($select))
{
$select = DB::select()->from($select);
}
if ( ! $select instanceof Database_Query_Builder_Select)
throw new Kohana_Exception('first parameter must be a string or an instance of Database_Query_Builder_Select');
$this->_union []= array('select' => $select, 'all' => $all);
return $this;
}
/**
* Start returning results after "OFFSET ..."
*
@@ -291,8 +326,8 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
*/
public function compile(Database $db)
{
// Callback to quote identifiers
$quote_ident = array($db, 'quote_identifier');
// Callback to quote columns
$quote_column = array($db, 'quote_column');
// Callback to quote tables
$quote_table = array($db, 'quote_table');
@@ -314,7 +349,7 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
else
{
// Select all columns
$query .= implode(', ', array_unique(array_map($quote_ident, $this->_select)));
$query .= implode(', ', array_unique(array_map($quote_column, $this->_select)));
}
if ( ! empty($this->_from))
@@ -338,7 +373,7 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
if ( ! empty($this->_group_by))
{
// Add sorting
$query .= ' GROUP BY '.implode(', ', array_map($quote_ident, $this->_group_by));
$query .= ' GROUP BY '.implode(', ', array_map($quote_column, $this->_group_by));
}
if ( ! empty($this->_having))
@@ -364,8 +399,22 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
// Add offsets
$query .= ' OFFSET '.$this->_offset;
}
if ( ! empty($this->_union))
{
foreach ($this->_union as $u) {
$query .= ' UNION ';
if ($u['all'] === TRUE)
{
$query .= 'ALL ';
}
$query .= $u['select']->compile($db);
}
}
return $query;
$this->_sql = $query;
return parent::compile($db);
}
public function reset()
@@ -376,7 +425,8 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
$this->_where =
$this->_group_by =
$this->_having =
$this->_order_by = array();
$this->_order_by =
$this->_union = array();
$this->_distinct = FALSE;
@@ -386,7 +436,10 @@ class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where
$this->_parameters = array();
$this->_sql = NULL;
return $this;
}
} // End Database_Query_Select

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for UPDATE statements.
* Database query builder for UPDATE statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query
@@ -97,13 +97,21 @@ class Kohana_Database_Query_Builder_Update extends Database_Query_Builder_Where
$query .= ' WHERE '.$this->_compile_conditions($db, $this->_where);
}
if ( ! empty($this->_order_by))
{
// Add sorting
$query .= ' '.$this->_compile_order_by($db, $this->_order_by);
}
if ($this->_limit !== NULL)
{
// Add limiting
$query .= ' LIMIT '.$this->_limit;
}
return $query;
$this->_sql = $query;
return parent::compile($db);
}
public function reset()
@@ -117,6 +125,8 @@ class Kohana_Database_Query_Builder_Update extends Database_Query_Builder_Where
$this->_parameters = array();
$this->_sql = NULL;
return $this;
}

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database query builder for WHERE statements.
* Database query builder for WHERE statements. See [Query Builder](/database/query/builder) for usage and examples.
*
* @package Kohana/Database
* @category Query

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database result wrapper.
* Database result wrapper. See [Results](/database/results) for usage and examples.
*
* @package Kohana/Database
* @category Query/Result
@@ -23,6 +23,9 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt
// Return rows as an object or associative array
protected $_as_object;
// Parameters for __construct when using object results
protected $_object_params = NULL;
/**
* Sets the total number of rows and stores the result locally.
*
@@ -30,7 +33,7 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt
* @param string SQL query
* @return void
*/
public function __construct($result, $sql, $as_object)
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL)
{
// Store the result locally
$this->_result = $result;
@@ -46,6 +49,12 @@ abstract class Kohana_Database_Result implements Countable, Iterator, SeekableIt
// Results as objects or associative arrays
$this->_as_object = $as_object;
if ($params)
{
// Object constructor params
$this->_object_params = $params;
}
}
/**

View File

@@ -1,6 +1,6 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Cached database result.
* Object used for caching the results of select queries. See [Results](/database/results#select-cached) for usage and examples.
*
* @package Kohana/Database
* @category Query/Result
@@ -45,7 +45,7 @@ class Kohana_Database_Result_Cached extends Database_Result {
public function current()
{
// Return an array of the row
return $this->_result[$this->_current_row];
return $this->valid() ? $this->_result[$this->_current_row] : NULL;
}
} // End Database_Result_Cached

View File

@@ -1,6 +1,17 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database object creation helper methods.
* Provides a shortcut to get Database related objects for [making queries](../database/query).
*
* Shortcut | Returned Object
* -------------|---------------
* [`DB::query()`](#query) | [Database_Query]
* [`DB::insert()`](#insert) | [Database_Query_Builder_Insert]
* [`DB::select()`](#select),<br />[`DB::select_array()`](#select_array) | [Database_Query_Builder_Select]
* [`DB::update()`](#update) | [Database_Query_Builder_Update]
* [`DB::delete()`](#delete) | [Database_Query_Builder_Delete]
* [`DB::expr()`](#expr) | [Database_Expression]
*
* You pass the same parameters to these functions as you pass to the objects they return.
*
* @package Kohana/Database
* @category Base
@@ -114,6 +125,8 @@ class Kohana_DB {
* is the only way to use SQL functions within query builders.
*
* $expression = DB::expr('COUNT(users.id)');
* $query = DB::update('users')->set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id);
* $users = ORM::factory('user')->where(DB::expr("BINARY `hash`"), '=', $hash)->find();
*
* @param string expression
* @return Database_Expression

View File

@@ -0,0 +1,58 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Database Model base class.
*
* @package Kohana/Database
* @category Models
* @author Kohana Team
* @copyright (c) 2008-2010 Kohana Team
* @license http://kohanaframework.org/license
*/
abstract class Kohana_Model_Database extends Model {
/**
* Create a new model instance. A [Database] instance or configuration
* group name can be passed to the model. If no database is defined, the
* "default" database group will be used.
*
* $model = Model::factory($name);
*
* @param string model name
* @param mixed Database instance object or string
* @return Model
*/
public static function factory($name, $db = NULL)
{
// Add the model prefix
$class = 'Model_'.$name;
return new $class($db);
}
// Database instance
protected $_db = 'default';
/**
* Loads the database.
*
* $model = new Foo_Model($db);
*
* @param mixed Database instance object or string
* @return void
*/
public function __construct($db = NULL)
{
if ($db !== NULL)
{
// Set the database instance name
$this->_db = $db;
}
if (is_string($this->_db))
{
// Load the database
$this->_db = Database::instance($this->_db);
}
}
} // End Model

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
abstract class Model_Database extends Kohana_Model_Database {}

View File

@@ -0,0 +1,23 @@
<?php defined('SYSPATH') or die('No direct script access.');
return array(
// Leave this alone
'modules' => array(
// This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename'
'database' => array(
// Whether this modules userguide pages should be shown
'enabled' => TRUE,
// The name that should show up on the userguide index page
'name' => 'Database',
// A short description of this module, shown on the index page
'description' => 'Database agnostic querying and result management.',
// Copyright message, shown in the footer for this module
'copyright' => '&copy; 20082010 Kohana Team',
)
)
);

View File

@@ -0,0 +1,118 @@
# Configuration
The default config file is located in `MODPATH/database/config/database.php`. You should copy this file to `APPPATH/config/database.php` and make changes there, in keeping with the [cascading filesystem](../kohana/files).
The database configuration file contains an array of configuration groups. The structure of each database configuration group, called an "instance", looks like this:
string INSTANCE_NAME => array(
'type' => string DATABASE_TYPE,
'connection' => array CONNECTION_ARRAY,
'table_prefix' => string TABLE_PREFIX,
'charset' => string CHARACTER_SET,
'profiling' => boolean QUERY_PROFILING,
),
Understanding each of these settings is important.
INSTANCE_NAME
: Connections can be named anything you want, but you should always have at least one connection called "default".
DATABASE_TYPE
: One of the installed database drivers. Kohana comes with "mysql" and "pdo" drivers. Drivers must extend the Database class.
CONNECTION_ARRAY
: Specific driver options for connecting to your database. (Driver options are explained [below](#connection-settings).)
TABLE_PREFIX
: Prefix that will be added to all table names by the [query builder](#query_building). Prepared statements will **not** use the table prefix.
QUERY_PROFILING
: Enables [profiling](../kohana/profiling) of database queries. This is useful for seeing how many queries each page is using, and which are taking the longest. You must enable the profiler the view these stats.
## Example
The example file below shows 2 MySQL connections, one local and one remote.
return array
(
'default' => array
(
'type' => 'mysql',
'connection' => array(
'hostname' => 'localhost',
'username' => 'dbuser',
'password' => 'mypassword',
'persistent' => FALSE,
'database' => 'my_db_name',
),
'table_prefix' => '',
'charset' => 'utf8',
'profiling' => TRUE,
),
'remote' => array(
'type' => 'mysql',
'connection' => array(
'hostname' => '55.55.55.55',
'username' => 'remote_user',
'password' => 'mypassword',
'persistent' => FALSE,
'database' => 'my_remote_db_name',
),
'table_prefix' => '',
'charset' => 'utf8',
'profiling' => TRUE,
),
);
## Connections and Instances
Each configuration group is referred to as a database instance. Each instance can be accessed by calling [Database::instance]. If you don't provide a parameter, the default instance is used.
// This would connect to the database defined as 'default'
$default = Database::instance();
// This would connect to the database defined as 'remote'
$remote = Database::instance('remote');
To disconnect the database, simply destroy the object:
unset($default)
// Or
unset(Database::$instances['default']);
If you want to disconnect all of the database instances at once:
Database::$instances = array();
## Connection Settings
Every database driver has different connection settings.
### MySQL
A [MySQL database](http://www.php.net/manual/en/book.mysql.php) can accept the following options in the `connection` array:
Type | Option | Description | Default value
----------|------------|----------------------------| -------------------------
`string` | hostname | Hostname of the database | `localhost`
`integer` | port | Port number | `NULL`
`string` | socket | UNIX socket | `NULL`
`string` | username | Database username | `NULL`
`string` | password | Database password | `NULL`
`boolean` | persistent | Persistent connections | `FALSE`
`string` | database | Database name | `kohana`
### PDO
A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options in the `connection` array:
Type | Option | Description | Default value
----------|------------|----------------------------| -------------------------
`string` | dsn | PDO data source identifier | `localhost`
`string` | username | Database username | `NULL`
`string` | password | Database password | `NULL`
`boolean` | persistent | Persistent connections | `FALSE`
[!!] If you are using PDO and are not sure what to use for the `dsn` option, review [PDO::__construct](http://php.net/pdo.construct).

View File

@@ -0,0 +1,52 @@
# Examples
Here are some "real world" examples of using the database library to construct your queries and use the results.
## Examples of Prepared Statements
TODO: 4-6 examples of prepared statements of varying complexity, including a good bind() example.
## Pagination and search/filter
In this example, we loop through an array of whitelisted input fields and for each allowed non-empty value we add it to the search query. We make a clone of the query and then execute that query to count the total number of results. The count is then passed to the [Pagination](../pagination) class to determine the search offset. The last few lines search with Pagination's items_per_page and offset values to return a page of results based on the current page the user is on.
$query = DB::select()->from('users');
//only search for these fields
$form_inputs = array('first_name', 'last_name', 'email');
foreach ($form_inputs as $name)
{
$value = Arr::get($_GET, $name, FALSE);
if ($value !== FALSE AND $value != '')
{
$query->where($name, 'like', '%'.$value.'%');
}
}
//copy the query & execute it
$pagination_query = clone $query;
$count = $pagination_query->select('COUNT("*") AS mycount')->execute()->get('mycount');
//pass the total item count to Pagination
$config = Kohana::config('pagination');
$pagination = Pagination::factory(array(
'total_items' => $count,
'current_page' => array('source' => 'route', 'key' => 'page'),
'items_per_page' => 20,
'view' => 'pagination/pretty',
'auto_hide' => TRUE,
));
$page_links = $pagination->render();
//search for results starting at the offset calculated by the Pagination class
$query->order_by('last_name', 'asc')
->order_by('first_name', 'asc')
->limit($pagination->items_per_page)
->offset($pagination->offset);
$results = $query->execute()->as_array();
## Having
TODO: example goes here
[!!] We could use more examples on this page.

View File

@@ -0,0 +1,17 @@
# Database
Kohana 3.0 comes with a robust module for working with databases. By default, the database module supports drivers for [MySQL](http://php.net/mysql) and [PDO](http://php.net/pdo), but new drivers can be made for other database servers.
The database module is included with the Kohana 3.0 install, but needs to be enabled before you can use it. To enable, open your `application/bootstrap.php` file and modify the call to [Kohana::modules] by including the database module like so:
Kohana::modules(array(
...
'database' => MODPATH.'database',
...
));
Next, you will then need to [configure](config) the database module to connect to your database.
Once that is done then you can make [queries](query) and use the [results](results).
The database module also provides a [config driver](../api/Kohana_Config_Database) (for storing [configuration](../kohana/files/config) in the database) and a [session driver](Session_Database).

View File

@@ -0,0 +1,7 @@
## [Database]()
- [Configuration](config)
- [Querying](query)
- [Prepared Statements](query/prepared)
- [Query Builder](query/builder)
- [Results](results)
- [Examples](examples)

View File

@@ -0,0 +1,5 @@
# Making Queries
There are two different ways to make queries. The simplest way to make a query is to use [Database_Query], via [DB::query], to manually create queries. These queries are called [prepared statements](query/prepared) and allow you to set query parameters which are automatically escaped. The second way to make a query is by building the query using method calls. This is done using the [query builder](query/builder).
[!!] All queries are run using the `execute` method, which accepts a [Database] object or instance name. See [Database_Query::execute] for more information.

View File

@@ -0,0 +1,253 @@
# Query Builder
Creating queries dynamically using objects and methods allows queries to be written very quickly in an agnostic way. Query building also adds identifier (table and column name) quoting, as well as value quoting.
[!!] At this time, it is not possible to combine query building with prepared statements.
## Select
Each type of database query is represented by a different class, each with their own methods. For instance, to create a SELECT query, we use [DB::select] which is a shortcut to return a new [Database_Query_Builder_Select] object:
$query = DB::select();
Query Builder methods return a reference to itself so that method chaining may be used. Select queries ussually require a table and they are referenced using the `from()` method. The `from()` method takes one parameter which can be the table name (string), an array of two strings (table name and alias), or an object (See Subqueries in the Advanced Queries section below).
$query = DB::select()->from('users');
Limiting the results of queries is done using the `where()`, `and_where()` and `or_where()` methods. These methods take three parameters: a column, an operator, and a value.
$query = DB::select()->from('users')->where('username', '=', 'john');
Multiple `where()` methods may be used to string together multiple clauses connected by the boolean operator in the method's prefix. The `where()` method is a wrapper that just calls `and_where()`.
$query = DB::select()->from('users')->where('username', '=', 'john')->or_where('username', '=', 'jane');
You can use any operator you want. Examples include `IN`, `BETWEEN`, `>`, `=<`, `!=`, etc. Use an array for operators that require more than one value.
$query = DB::select()->from('users')->where('logins', '<=', 1);
$query = DB::select()->from('users')->where('logins', '>', 50);
$query = DB::select()->from('users')->where('username', 'IN', array('john','mark','matt'));
$query = DB::select()->from('users')->where('joindate', 'BETWEEN', array($then, $now));
By default, [DB::select] will select all columns (`SELECT * ...`), but you can also specify which columns you want returned by passing parameters to [DB::select]:
$query = DB::select('username', 'password')->from('users')->where('username', '=', 'john');
Now take a minute to look at what this method chain is doing. First, we create a new selection object using the [DB::select] method. Next, we set table(s) using the `from()` method. Last, we search for a specific records using the `where()` method. We can display the SQL that will be executed by casting the query to a string:
echo Kohana::debug((string) $query);
// Should display:
// SELECT `username`, `password` FROM `users` WHERE `username` = 'john'
Notice how the column and table names are automatically escaped, as well as the values? This is one of the key benefits of using the query builder.
### Select - AS (column aliases)
It is also possible to create `AS` aliases when selecting, by passing an array as each parameter to [DB::select]:
$query = DB::select(array('username', 'u'), array('password', 'p'))->from('users');
This query would generate the following SQL:
SELECT `username` AS `u`, `password` AS `p` FROM `users`
### Select - DISTINCT
Unique column values may be turned on or off (default) by passing TRUE or FALSE, respectively, to the `distinct()` method.
$query = DB::select('username')->distinct(TRUE)->from('posts');
This query would generate the following SQL:
SELECT DISTINCT `username` FROM `posts`
### Select - LIMIT & OFFSET
When querying large sets of data, it is often better to limit the results and page through the data one chunk at a time. This is done using the `limit()` and `offset()` methods.
$query = DB::select()->from(`posts`)->limit(10)->offset(30);
This query would generate the following SQL:
SELECT * FROM `posts` LIMIT 10 OFFSET 30
### Select - ORDER BY
Often you will want the results in a particular order and rather than sorting the results, it's better to have the results returned to you in the correct order. You can do this by using the order_by() method. It takes the column name and an optional direction string as the parameters. Multiple `order_by()` methods can be used to add additional sorting capability.
$query = DB::select()->from(`posts`)->order_by(`published`, `DESC`);
This query would generate the following SQL:
SELECT * FROM `posts` ORDER BY `published` DESC
[!!] For a complete list of methods available while building a select query see [Database_Query_Builder_Select].
## Insert
To create records into the database, use [DB::insert] to create an INSERT query, using `values()` to pass in the data:
$query = DB::insert('users', array('username', 'password'))->values(array('fred', 'p@5sW0Rd'));
This query would generate the following SQL:
INSERT INTO `users` (`username`, `password`) VALUES ('fred', 'p@5sW0Rd')
[!!] For a complete list of methods available while building an insert query see [Database_Query_Builder_Insert].
## Update
To modify an existing record, use [DB::update] to create an UPDATE query:
$query = DB::update('users')->set(array('username' => 'jane'))->where('username', '=', 'john');
This query would generate the following SQL:
UPDATE `users` SET `username` = 'jane' WHERE `username` = 'john'
[!!] For a complete list of methods available while building an update query see [Database_Query_Builder_Update].
## Delete
To remove an existing record, use [DB::delete] to create a DELETE query:
$query = DB::delete('users')->where('username', 'IN', array('john', 'jane'));
This query would generate the following SQL:
DELETE FROM `users` WHERE `username` IN ('john', 'jane')
[!!] For a complete list of methods available while building a delete query see [Database_Query_Builder_Delete].
## Advanced Queries
### Joins
Multiple tables can be joined using the `join()` and `on()` methods. The `join()` method takes two parameters. The first is either a table name, an array containing the table and alias, or an object (subquery or expression). The second parameter is the join type: LEFT, RIGHT, INNER, etc.
The `on()` method sets the conditions for the previous `join()` method and is very similar to the `where()` method in that it takes three parameters; left column (name or object), an operator, and the right column (name or object). Multiple `on()` methods may be used to supply multiple conditions and they will be appended with an 'AND' operator.
// This query will find all the posts related to "smith" with JOIN
$query = DB::select('authors.name', 'posts.content')->from('authors')->join('posts')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith');
This query would generate the following SQL:
SELECT `authors`.`name`, `posts`.`content` FROM `authors` JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith'
If you want to do a LEFT, RIGHT or INNER JOIN you would do it like this `join('colum_name', 'type_of_join')`:
// This query will find all the posts related to "smith" with LEFT JOIN
$query = DB::select()->from('authors')->join('posts', 'LEFT')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith');
This query would generate the following SQL:
SELECT `authors`.`name`, `posts`.`content` FROM `authors` LEFT JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith'
[!!] When joining multiple tables with similar column names, it's best to prefix the columns with the table name or table alias to avoid errors. Ambiguous column names should also be aliased so that they can be referenced easier.
### Database Functions
Eventually you will probably run into a situation where you need to call `COUNT` or some other database function within your query. The query builder supports these functions in two ways. The first is by using quotes within aliases:
$query = DB::select(array('COUNT("username")', 'total_users'))->from('users');
This looks almost exactly the same as a standard `AS` alias, but note how the column name is wrapped in double quotes. Any time a double-quoted value appears inside of a column name, **only** the part inside the double quotes will be escaped. This query would generate the following SQL:
SELECT COUNT(`username`) AS `total_users` FROM `users`
[!!] When building complex queries and you need to get a count of the total rows that will be returned, build the expression with an empty column list first. Then clone the query and add the COUNT function to one copy and the columns list to the other. This will cut down on the total lines of code and make updating the query easier.
$query = DB::select()->from('users')
->join('posts')->on('posts.username', '=', 'users.username')
->where('users.active', '=', TRUE)
->where('posts.created', '>=', $yesterday);
$total = clone $query;
$total->select(array('COUNT( DISTINCT "username")', 'unique_users'));
$query->select('posts.username')->distinct();
### Aggregate Functions
Aggregate functions like `COUNT()`, `SUM()`, `AVG()`, etc. will most likely be used with the `group_by()` and possibly the `having()` methods in order to group and filter the results on a set of columns.
$query = DB::select('username', array('COUNT("id")', 'total_posts')
->from('posts')->group_by('username')->having('total_posts', '>=', 10);
This will generate the following query:
SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10
### Subqueries
Query Builder objects can be passed as parameters to many of the methods to create subqueries. Let's take the previous example query and pass it to a new query.
$sub = DB::select('username', array('COUNT("id")', 'total_posts')
->from('posts')->group_by('username')->having('total_posts', '>=', 10);
$query = DB::select('profiles.*', 'posts.total_posts')->from('profiles')
->join(array($sub, 'posts'), 'INNER')->on('profiles.username', '=', 'posts.username');
This will generate the following query:
SELECT `profiles`.*, `posts`.`total_posts` FROM `profiles` INNER JOIN
( SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 ) AS posts
ON `profiles`.`username` = `posts`.`username`
Insert queries can also use a select query for the input values
$sub = DB::select('username', array('COUNT("id")', 'total_posts')
->from('posts')->group_by('username')->having('total_posts', '>=', 10);
$query = DB::insert('post_totals', array('username', 'posts'))->select($sub);
This will generate the following query:
INSERT INTO `post_totals` (`username`, `posts`)
SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10
### Boolean Operators and Nested Clauses
Multiple Where and Having clauses are added to the query with Boolean operators connecting each expression. The default operator for both methods is AND which is the same as the and_ prefixed method. The OR operator can be specified by prefixing the methods with or_. Where and Having clauses can be nested or grouped by post fixing either method with _open and then followed by a method with a _close.
$query = DB::select()->from('users')
->where_open()
->or_where('id', 'IN', $expired)
->and_where_open()
->where('last_login', '<=', $last_month)
->or_where('last_login', 'IS', NULL)
->and_where_close()
->where_close()
->and_where('removed','IS', NULL);
This will generate the following query:
SELECT * FROM `users` WHERE ( `id` IN (1, 2, 3, 5) OR ( `last_login` <= 1276020805 OR `last_login` IS NULL ) ) AND `removed` IS NULL
### Database Expressions
There are cases were you need a complex expression or other database functions, which you don't want the Query Builder to try and escape. In these cases, you will need to use a database expression created with [DB::expr]. **A database expression is taken as direct input and no escaping is performed.**
$query = DB::update('users')->set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id);
This will generate the following query, assuming `$id = 45`:
UPDATE `users` SET `login_count` = `login_count` + 1 WHERE `id` = 45
Another example to calculate the distance of two geographical points:
$query = DB::select(array(DB::expr('degrees(acos(sin(radians('.$lat.')) * sin(radians(`latitude`)) + cos(radians('.$lat.')) * cos(radians(`latitude`)) * cos(radians(abs('.$lng.' - `longitude`))))) * 69.172'), 'distance'))->from('locations');
[!!] You must validate or escape any user input inside of DB::expr as it will obviously not be escaped it for you.
## Executing
Once you are done building, you can execute the query using `execute()` and use [the results](results).
$result = $query->execute();
To use a different database [config group](config) pass either the name or the config object to `execute()`.
$result = $query->execute('config_name')

View File

@@ -0,0 +1,67 @@
# Prepared Statements
Using prepared statements allows you to write SQL queries manually while still escaping the query values automatically to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Creating a query is simple:
$query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user');
The [DB::query] method is just a shortcut that creates a new [Database_Query] class for us, to allow method chaining. The query contains a `:user` parameter, which we will get to in a second.
The first parameter of [DB::query] is the type of query. It should be `Database::SELECT`, `Database::INSERT`, `Database::UPDATE`, or `Database::DELETE`. This is done for compatibility reasons for drivers, and to easily determine what `execute()` should return.
The second parameter is the query itself. Rather than trying to concatenate your query and variables together, you should make use of [Database_Query::param]. This will make your queries much easier to mantain, and will escape the values to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection).
## Parameters
Our example query earlier contains a `:user` parameter, which we can assign to a value using [Database_Query::param] like so:
$query->param(':user', 'john');
[!!] Parameter names can be any unique string, as they are replaced using [strtr](http://php.net/strtr). It is highly recommended to **not** use dollars signs as parameter names to prevent confusion. Colons are commonly used.
You can also update the `:user` parameter by calling [Database_Query::param] again:
$query->param(':user', $_GET['search']);
If you want to set multiple parameters at once, you can use [Database_Query::parameters].
$query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user AND status = :status');
$query->parameters(array(
':user' => 'john',
':status' => 'active',
));
It is also possible to bind a parameter to a variable, using a [variable reference]((http://php.net/language.references.whatdo)). This can be extremely useful when running the same query many times:
$query = DB::query(Database::INSERT, 'INSERT INTO users (username, password) VALUES (:user, :pass)')
->bind(':user', $username)
->bind(':pass', $password);
foreach ($new_users as $username => $password)
{
$query->execute();
}
In the above example, the variables `$username` and `$password` are changed for every loop of the `foreach` statement. When the parameter changes, it effectively changes the `:user` and `:pass` query parameters. Careful parameter binding can save a lot of code when it is used properly.
The only difference between `param()` and `bind()` is that `bind()` passes the variable by reference rather than by assignment (copied), so future changes to the variable can be "seen" by the query.
[!!] Although all parameters are escaped to prevent SQL injection, it is still a good idea to validate/sanitize your input.
## Display the raw query
If you want to display the SQL that will be executed, simply cast the object to a string:
echo Kohana::debug((string) $query);
// Should display:
// SELECT * FROM users WHERE username = 'john'
## Executing
Once you have assigned something to each of the parameters, you can execute the query using `execute()` and use [the results](results).
$result = $query->execute();
To use a different database [config group](config) pass either the name or the config object to `execute()`.
$result = $query->execute('config_name')

View File

@@ -0,0 +1,105 @@
# Results
## Execute
Once you have a query object built, either through a prepared statement or through the builder, you must then `execute()` the query and retrieve the results. Depending on the query type used, the results returned will vary.
## Select
[DB::select] will return a [Database_Result] object which you can then iterate over. This example shows how you can iterate through the [Database_Result] using a foreach.
$results = DB::select()->from('users')->where('verified', '=', 0)->execute();
foreach($results as $user)
{
// Send reminder email to $user['email']
echo $user['email']." needs to verify his/her account\n";
}
### Select - `as_object()` and `as_assoc()`
When iterating over a result set, the default type will be an associative array with the column names or aliases as the keys. As an option, before calling `execute()`, you can specify to return the result rows as an object by using the `as_object()` method. The `as_object()` method takes one parameter, the name of the class of your choice, but will default to TRUE which uses the `stdClass`. Here is the example again using `stdClass`.
$results = DB::select()->from('users')->where('verified', '=', 0)->as_object()->execute();
foreach($results as $user)
{
// Send reminder email to $user->email
echo $user->email." needs to verify his/her account\n";
}
[!!] The method `as_assoc()` will remove the object name and return the results set back to an associative array. Since this is the default, this method is seldom required.
### Select - `as_array()`
Sometimes you will require the results as a pure array rather than as an object. The `Database_Result` method `as_array()` will return an array of all rows.
$results = DB::select('id', 'email')->from('users')->execute();
$users = $results->as_array();
foreach($users as $user)
{
echo 'User ID: '.$user['id'];
echo 'User Email: '.$user['email'];
}
It also accepts two parameters that can be very helpful: `$key` and `$value`. When passing a value to `$key` you will index the resulting array by the column specified.
$results = DB::select('id', 'email')->from('users')->execute();
$users = $results->as_array('id');
foreach($users as $id => $user)
{
echo 'User ID: '.$id;
echo 'User Email: '.$user['email'];
}
The second parameter, `$value`, will reference the column specified and return that value rather than the whole row. This is particularly useful when making `<select>` dropdowns.
$results = DB::select('id', 'name')->from('users')->execute();
$users = $results->as_array('id','name');
// Show a dropdown with all users in it.
echo Form::select('author', $users)
To return a non-associative array, leave `$key` as NULL and just pass a `$value`.
$results = DB::select('email')->from('users')->execute();
$users = $results->as_array(NULL, 'email');
foreach($users as $email)
{
echo 'User Email: '.$email;
}
### Select - `get()`
Sometime you only want a single value from a query. The `get()` method returns the value of the named column from the current row. The second parameter, `$default`, is used to supply a default value when the result is NULL.
$total_users = DB::select(array('COUNT("username")', 'total_users'))->from('users')->execute()->get('total_users', 0);
### Select - `cached()`
The mysql database driver returns a `Database_Result` that works with a MySQL Resource data type. Since this resource lives outside of PHP environment, it can't be serialized which means it also can't be cached. To get around this the `Database_Result` object has the `cached()` method that returns a `Database_Result_Cached` object of the result set. The `Database_Result_Cached` can be serialized and cached, but can take up more memory.
[!!] NOTE: Currently, the PDO diver always returns a class of `Database_Result_Cached`, so `cached()` just returns itself.
The `cached()` function doesn't actually do any caching, it simply returns the result in a way that can be serialized and cached. You will need to use the [Cache Module](../cache) or some other caching method.
### Select - `count()`
The `Database_Result` object implements the `Countable` Interface. The method `count()` returns the total row count in the result set.
[!!] NOTE: This is the count of the current result set, not a count of how many records are in the database. This is important to point out especially when using `limit()` and `offset()` in your query.
[!!] For a complete list of methods available when working with a result set see [Database_Result].
## Insert
[DB::insert] returns an array of two values: the last insert id and the number of affected rows.
$insert = DB::insert('tools')
->columns(array('name', 'model', 'description'))
->values(array('Skil 3400 10" Table Saw', '3400', 'Powerful 15 amp motor; weighs just 54-pounds'));
list($insert_id, $affected_rows) = $insert->execute();
## Update & Delete
[DB::update] and [DB::delete] both return the number of affected rows as an integer.
$rows_deleted = DB::delete('tools')->where('model', 'like', '3400')->execute();