diff --git a/classes/config/database.php b/classes/config/database.php index 776a801..bca215a 100644 --- a/classes/config/database.php +++ b/classes/config/database.php @@ -1,3 +1,12 @@ _database_instance = $config['instance']; - } - - if (isset($config['table'])) - { - $this->_database_table = $config['table']; - } - - parent::__construct(); - } - - /** - * Query the configuration table for all values for this group and - * unserialize each of the values. - * - * @param string group name - * @param array configuration array - * @return $this clone of the current object - */ - public function load($group, array $config = NULL) - { - if ($config === NULL AND $group !== 'database') - { - // Load all of the configuration values for this group - $query = DB::select('config_key', 'config_value') - ->from($this->_database_table) - ->where('group_name', '=', $group) - ->execute($this->_database_instance); - - if (count($query) > 0) - { - // Unserialize the configuration values - $config = array_map('unserialize', $query->as_array('config_key', 'config_value')); - } - } - - return parent::load($group, $config); - } - - /** - * Overload setting offsets to insert or update the database values as - * changes occur. - * - * @param string array key - * @param mixed new value - * @return mixed - */ - public function offsetSet($key, $value) - { - if ( ! $this->offsetExists($key)) - { - // Insert a new value - DB::insert($this->_database_table, array('group_name', 'config_key', 'config_value')) - ->values(array($this->_configuration_group, $key, serialize($value))) - ->execute($this->_database_instance); - } - elseif ($this->offsetGet($key) !== $value) - { - // Update the value - DB::update($this->_database_table) - ->value('config_value', serialize($value)) - ->where('group_name', '=', $this->_configuration_group) - ->where('config_key', '=', $key) - ->execute($this->_database_instance); - } - - return parent::offsetSet($key, $value); - } - -} // End Kohana_Config_Database +class Kohana_Config_Database extends Kohana_Config_Database_Writer +{ + +} diff --git a/classes/kohana/config/database/reader.php b/classes/kohana/config/database/reader.php new file mode 100644 index 0000000..5a5a9a7 --- /dev/null +++ b/classes/kohana/config/database/reader.php @@ -0,0 +1,53 @@ +_db_instance = $config['instance']; + } + + if (isset($config['table_name'])) + { + $this->_table_name = $config['table_name']; + } + } + + /** + * Tries to load the specificed configuration group + * + * Returns FALSE if group does not exist or an array if it does + * + * @param string $group Configuration group + * @return boolean|array + */ + public function load($group) + { + $query = DB::select('config_key', 'config_value') + ->from($this->_table_name) + ->where('group_name', '=', $group) + ->execute($this->_db_instance); + + return count($query) ? array_map('unserialize', $query->as_array('config_key', 'config_value')) : FALSE; + } +} diff --git a/classes/kohana/config/database/writer.php b/classes/kohana/config/database/writer.php new file mode 100644 index 0000000..52d9dd9 --- /dev/null +++ b/classes/kohana/config/database/writer.php @@ -0,0 +1,110 @@ +_loaded_keys[$group] = array_combine(array_keys($config), array_keys($config)); + } + + return $config; + } + + /** + * Writes the passed config for $group + * + * Returns chainable instance on success or throws + * Kohana_Config_Exception on failure + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The configuration to write + * @return boolean + */ + public function write($group, $key, $config) + { + $config = serialize($config); + + // Check to see if we've loaded the config from the table already + if (isset($this->_loaded_keys[$group][$key])) + { + $this->_update($group, $key, $config); + } + else + { + // Attempt to run an insert query + // This may fail if the config key already exists in the table + // and we don't know about it + try + { + $this->_insert($group, $key, $config); + } + catch (Database_Exception $e) + { + // Attempt to run an update instead + $this->_update($group, $key, $config); + } + } + + return TRUE; + } + + /** + * Insert the config values into the table + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The serialized configuration to write + * @return boolean + */ + protected function _insert($group, $key, $config) + { + DB::insert($this->_table_name, array('group_name', 'config_key', 'config_value')) + ->values(array($group, $key, $config)) + ->execute($this->_db_instance); + + return $this; + } + + /** + * Update the config values in the table + * + * @param string $group The config group + * @param string $key The config key to write to + * @param array $config The serialized configuration to write + * @return boolean + */ + protected function _update($group, $key, $config) + { + DB::update($this->_table_name) + ->set(array('config_value' => $config)) + ->where('group_name', '=', $group) + ->where('config_key', '=', $key) + ->execute($this->_db_instance); + + return $this; + } +} diff --git a/classes/kohana/database.php b/classes/kohana/database.php index d84420a..b7b7acf 100644 --- a/classes/kohana/database.php +++ b/classes/kohana/database.php @@ -62,7 +62,7 @@ public static function instance($name = NULL, array $config = NULL) if ($config === NULL) { // Load the configuration for this database - $config = Kohana::config('database')->$name; + $config = Kohana::$config->load('database')->$name; } if ( ! isset($config['type'])) @@ -416,7 +416,7 @@ public function table_prefix() * $db->quote('fred'); // 'fred' * * Objects passed to this function will be converted to strings. - * [Database_Expression] objects will use the value of the expression. + * [Database_Expression] objects will be compiled. * [Database_Query] objects will be compiled and converted to a sub-query. * All other objects will be converted using the `__toString` method. * @@ -447,8 +447,8 @@ public function quote($value) } elseif ($value instanceof Database_Expression) { - // Use a raw expression - return $value->value(); + // Compile the expression + return $value->compile($this); } else { @@ -483,6 +483,11 @@ public function quote($value) * // The value of "column" will be quoted * $column = $db->quote_column('COUNT("column")'); * + * Objects passed to this function will be converted to strings. + * [Database_Expression] objects will be compiled. + * [Database_Query] objects will be compiled and converted to a sub-query. + * All other objects will be converted using the `__toString` method. + * * @param mixed column name or array(column, alias) * @return string * @uses Database::quote_identifier @@ -502,8 +507,8 @@ public function quote_column($column) } elseif ($column instanceof Database_Expression) { - // Use a raw expression - $column = $column->value(); + // Compile the expression + $column = $column->compile($this); } else { @@ -562,6 +567,11 @@ public function quote_column($column) * * $table = $db->quote_table($table); * + * Objects passed to this function will be converted to strings. + * [Database_Expression] objects will be compiled. + * [Database_Query] objects will be compiled and converted to a sub-query. + * All other objects will be converted using the `__toString` method. + * * @param mixed table name or array(table, alias) * @return string * @uses Database::quote_identifier @@ -581,8 +591,8 @@ public function quote_table($table) } elseif ($table instanceof Database_Expression) { - // Use a raw expression - $table = $table->value(); + // Compile the expression + $table = $table->compile($this); } else { @@ -630,7 +640,7 @@ public function quote_table($table) * Quote a database identifier * * Objects passed to this function will be converted to strings. - * [Database_Expression] objects will use the value of the expression. + * [Database_Expression] objects will be compiled. * [Database_Query] objects will be compiled and converted to a sub-query. * All other objects will be converted using the `__toString` method. * @@ -651,8 +661,8 @@ public function quote_identifier($value) } elseif ($value instanceof Database_Expression) { - // Use a raw expression - $value = $value->value(); + // Compile the expression + $value = $value->compile($this); } else { diff --git a/classes/kohana/database/expression.php b/classes/kohana/database/expression.php index 90b8100..b61b85a 100644 --- a/classes/kohana/database/expression.php +++ b/classes/kohana/database/expression.php @@ -18,6 +18,9 @@ */ class Kohana_Database_Expression { + // Unquoted parameters + protected $_parameters; + // Raw expression string protected $_value; @@ -26,12 +29,56 @@ class Kohana_Database_Expression { * * $expression = new Database_Expression('COUNT(users.id)'); * + * @param string $value raw SQL expression string + * @param array $parameters unquoted parameter values * @return void */ - public function __construct($value) + public function __construct($value, $parameters = array()) { // Set the expression string $this->_value = $value; + $this->_parameters = $parameters; + } + + /** + * Bind a variable to a parameter. + * + * @param string $param parameter key to replace + * @param mixed $var variable to use + * @return $this + */ + public function bind($param, & $var) + { + $this->_parameters[$param] =& $var; + + return $this; + } + + /** + * Set the value of a parameter. + * + * @param string $param parameter key to replace + * @param mixed $value value to use + * @return $this + */ + public function param($param, $value) + { + $this->_parameters[$param] = $value; + + return $this; + } + + /** + * Add multiple parameter values. + * + * @param array $params list of parameter values + * @return $this + */ + public function parameters(array $params) + { + $this->_parameters = $params + $this->_parameters; + + return $this; } /** @@ -59,4 +106,33 @@ public function __toString() return $this->value(); } + /** + * Compile the SQL expression and return it. Replaces any parameters with + * their given values. + * + * @param mixed Database instance or name of instance + * @return string + */ + public function compile($db = NULL) + { + if ( ! is_object($db)) + { + // Get the database instance + $db = Database::instance($db); + } + + $value = $this->value(); + + if ( ! empty($this->_parameters)) + { + // Quote all of the parameter values + $params = array_map(array($db, 'quote'), $this->_parameters); + + // Replace the values in the expression + $value = strtr($value, $params); + } + + return $value; + } + } // End Database_Expression diff --git a/classes/kohana/database/mysql.php b/classes/kohana/database/mysql.php index 4fdbf01..f80e923 100644 --- a/classes/kohana/database/mysql.php +++ b/classes/kohana/database/mysql.php @@ -59,14 +59,14 @@ public function connect() $this->_connection = mysql_connect($hostname, $username, $password, TRUE); } } - catch (ErrorException $e) + catch (Exception $e) { // No connection exists $this->_connection = NULL; throw new Database_Exception(':error', - array(':error' => mysql_error()), - mysql_errno()); + array(':error' => $e->getMessage()), + $e->getCode()); } // \xFF is a better delimiter, but the PHP driver uses underscore @@ -79,6 +79,19 @@ public function connect() // Set the character set $this->set_charset($this->_config['charset']); } + + if ( ! empty($this->_config['connection']['variables'])) + { + // Set session variables + $variables = array(); + + foreach ($this->_config['connection']['variables'] as $var => $val) + { + $variables[] = 'SESSION '.$var.' = '.$this->quote($val); + } + + mysql_query('SET '.implode(', ', $variables), $this->_connection); + } } /** diff --git a/classes/kohana/database/pdo.php b/classes/kohana/database/pdo.php index 4f631b6..c4ec050 100644 --- a/classes/kohana/database/pdo.php +++ b/classes/kohana/database/pdo.php @@ -68,6 +68,51 @@ public function connect() } } + /** + * Create or redefine a SQL aggregate function. + * + * [!!] Works only with SQLite + * + * @link http://php.net/manual/function.pdo-sqlitecreateaggregate + * + * @param string $name Name of the SQL function to be created or redefined + * @param callback $step Called for each row of a result set + * @param callback $final Called after all rows of a result set have been processed + * @param integer $arguments Number of arguments that the SQL function takes + * + * @return boolean + */ + public function create_aggregate($name, $step, $final, $arguments = -1) + { + $this->_connection or $this->connect(); + + return $this->_connection->sqliteCreateAggregate( + $name, $step, $final, $arguments + ); + } + + /** + * Create or redefine a SQL function. + * + * [!!] Works only with SQLite + * + * @link http://php.net/manual/function.pdo-sqlitecreatefunction + * + * @param string $name Name of the SQL function to be created or redefined + * @param callback $callback Callback which implements the SQL function + * @param integer $arguments Number of arguments that the SQL function takes + * + * @return boolean + */ + public function create_function($name, $callback, $arguments = -1) + { + $this->_connection or $this->connect(); + + return $this->_connection->sqliteCreateFunction( + $name, $callback, $arguments + ); + } + public function disconnect() { // Destroy the PDO object diff --git a/classes/kohana/database/query.php b/classes/kohana/database/query.php index 96db093..e9f4486 100644 --- a/classes/kohana/database/query.php +++ b/classes/kohana/database/query.php @@ -13,6 +13,9 @@ class Kohana_Database_Query { // Query type protected $_type; + // Execute the query during a cache hit + protected $_force_execute = FALSE; + // Cache lifetime protected $_lifetime = NULL; @@ -72,11 +75,12 @@ public function type() /** * Enables the query to be cached for a specified amount of time. * - * @param integer number of seconds to cache + * @param integer number of seconds to cache, 0 deletes it from the cache + * @param boolean whether or not to execute the query during a cache hit * @return $this * @uses Kohana::$cache_life */ - public function cached($lifetime = NULL) + public function cached($lifetime = NULL, $force = FALSE) { if ($lifetime === NULL) { @@ -84,6 +88,7 @@ public function cached($lifetime = NULL) $lifetime = Kohana::$cache_life; } + $this->_force_execute = $force; $this->_lifetime = $lifetime; return $this; @@ -194,11 +199,14 @@ public function compile(Database $db) * Execute the current query on the given database. * * @param mixed Database instance or name of instance + * @param string result object classname, TRUE for stdClass or FALSE for array + * @param array result object constructor arguments + * * @return object Database_Result for SELECT queries * @return mixed the insert id for INSERT queries * @return integer number of affected rows for all other queries */ - public function execute($db = NULL) + public function execute($db = NULL, $as_object = NULL, $object_params = NULL) { if ( ! is_object($db)) { @@ -206,6 +214,16 @@ public function execute($db = NULL) $db = Database::instance($db); } + if ($as_object === NULL) + { + $as_object = $this->_as_object; + } + + if ($object_params === NULL) + { + $object_params = $this->_object_params; + } + // Compile the SQL query $sql = $this->compile($db); @@ -214,17 +232,19 @@ public function execute($db = NULL) // Set the cache key based on the database instance name and SQL $cache_key = 'Database::query("'.$db.'", "'.$sql.'")'; - if ($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) + // Read the cache first to delete a possible hit with lifetime <= 0 + if (($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL + AND ! $this->_force_execute) { // Return a cached result - return new Database_Result_Cached($result, $sql, $this->_as_object, $this->_object_params); + return new Database_Result_Cached($result, $sql, $as_object, $object_params); } } // Execute the query - $result = $db->query($this->_type, $sql, $this->_as_object, $this->_object_params); + $result = $db->query($this->_type, $sql, $as_object, $object_params); - if (isset($cache_key)) + if (isset($cache_key) AND $this->_lifetime > 0) { // Cache the result array Kohana::cache($cache_key, $result->as_array(), $this->_lifetime); diff --git a/classes/kohana/database/query/builder.php b/classes/kohana/database/query/builder.php index 89fbbd8..d41b23a 100644 --- a/classes/kohana/database/query/builder.php +++ b/classes/kohana/database/query/builder.php @@ -118,8 +118,16 @@ protected function _compile_conditions(Database $db, array $conditions) if ($column) { - // Apply proper quoting to the column - $column = $db->quote_column($column); + if (is_array($column)) + { + // Use the column name + $column = $db->quote_identifier(reset($column)); + } + else + { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } } // Append the statement to the query @@ -163,6 +171,36 @@ protected function _compile_set(Database $db, array $values) return implode(', ', $set); } + /** + * Compiles an array of GROUP BY columns into an SQL partial. + * + * @param object Database instance + * @param array columns + * @return string + */ + protected function _compile_group_by(Database $db, array $columns) + { + $group = array(); + + foreach ($columns as $column) + { + if (is_array($column)) + { + // Use the column alias + $column = $db->quote_identifier(end($column)); + } + else + { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } + + $group[] = $column; + } + + return 'GROUP BY '.implode(', ', $group); + } + /** * Compiles an array of ORDER BY statements into an SQL partial. * @@ -177,19 +215,24 @@ protected function _compile_order_by(Database $db, array $columns) { list ($column, $direction) = $group; - if ($direction) + if (is_array($column)) { - // Make the direction uppercase - $direction = strtoupper($direction); + // Use the column alias + $column = $db->quote_identifier(end($column)); } - - if ($column) + else { - // Quote the column, if it has a value + // Apply proper quoting to the column $column = $db->quote_column($column); } - $sort[] = trim($column.' '.$direction); + if ($direction) + { + // Make the direction uppercase + $direction = ' '.strtoupper($direction); + } + + $sort[] = $column.$direction; } return 'ORDER BY '.implode(', ', $sort); diff --git a/classes/kohana/database/query/builder/select.php b/classes/kohana/database/query/builder/select.php index 4c45624..e4ef340 100644 --- a/classes/kohana/database/query/builder/select.php +++ b/classes/kohana/database/query/builder/select.php @@ -308,12 +308,12 @@ public function union($select, $all = TRUE) /** * Start returning results after "OFFSET ..." * - * @param integer starting result number + * @param integer starting result number or NULL to reset * @return $this */ public function offset($number) { - $this->_offset = (int) $number; + $this->_offset = $number; return $this; } @@ -372,8 +372,8 @@ public function compile(Database $db) if ( ! empty($this->_group_by)) { - // Add sorting - $query .= ' GROUP BY '.implode(', ', array_map($quote_column, $this->_group_by)); + // Add grouping + $query .= ' '.$this->_compile_group_by($db, $this->_group_by); } if ( ! empty($this->_having)) @@ -442,4 +442,3 @@ public function reset() } } // End Database_Query_Select - diff --git a/classes/kohana/database/query/builder/where.php b/classes/kohana/database/query/builder/where.php index aeece75..292e01a 100644 --- a/classes/kohana/database/query/builder/where.php +++ b/classes/kohana/database/query/builder/where.php @@ -147,12 +147,12 @@ public function order_by($column, $direction = NULL) /** * Return up to "LIMIT ..." results * - * @param integer maximum results to return + * @param integer maximum results to return or NULL to reset * @return $this */ public function limit($number) { - $this->_limit = (int) $number; + $this->_limit = $number; return $this; } diff --git a/classes/kohana/db.php b/classes/kohana/db.php index b6e513f..7b04130 100644 --- a/classes/kohana/db.php +++ b/classes/kohana/db.php @@ -129,11 +129,12 @@ public static function delete($table = NULL) * $users = ORM::factory('user')->where(DB::expr("BINARY `hash`"), '=', $hash)->find(); * * @param string expression + * @param array parameters * @return Database_Expression */ - public static function expr($string) + public static function expr($string, $parameters = array()) { - return new Database_Expression($string); + return new Database_Expression($string, $parameters); } } // End DB diff --git a/classes/kohana/session/database.php b/classes/kohana/session/database.php index 1a381cd..c219a00 100644 --- a/classes/kohana/session/database.php +++ b/classes/kohana/session/database.php @@ -176,6 +176,16 @@ protected function _write() return TRUE; } + /** + * @return bool + */ + protected function _restart() + { + $this->_regenerate(); + + return TRUE; + } + protected function _destroy() { if ($this->_update_id === NULL) diff --git a/config/database.php b/config/database.php index 69cb827..627af81 100644 --- a/config/database.php +++ b/config/database.php @@ -14,6 +14,7 @@ * string username database username * string password database password * boolean persistent use persistent connections? + * array variables system variables as "key => value" pairs * * Ports and sockets may be appended to the hostname. */ diff --git a/guide/database/examples.md b/guide/database/examples.md index 4849823..4587ddc 100644 --- a/guide/database/examples.md +++ b/guide/database/examples.md @@ -28,7 +28,7 @@ In this example, we loop through an array of whitelisted input fields and for ea $count = $pagination_query->select('COUNT("*") AS mycount')->execute()->get('mycount'); //pass the total item count to Pagination - $config = Kohana::config('pagination'); + $config = Kohana::$config->load('pagination'); $pagination = Pagination::factory(array( 'total_items' => $count, 'current_page' => array('source' => 'route', 'key' => 'page'),