Got changelog in sync with changes after v2.0.3 release branch merged a bunch of stuff. Anyone moving a develop change into v2.0.3 should move the changelog item too.
diff --git a/application/config/database.php b/application/config/database.php
index fa541a7..b4b34bf 100644
--- a/application/config/database.php
+++ b/application/config/database.php
@@ -27,7 +27,8 @@
 |	['char_set'] The character set used in communicating with the database
 |	['dbcollat'] The character collation used in communicating with the database
 |				 NOTE: For MySQL and MySQLi databases, this setting is only used
-| 				 as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7.
+| 				 as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
+|				 (and in table creation queries made with DB Forge).
 | 				 There is an incompatibility in PHP with mysql_real_escape_string() which
 | 				 can make your site vulnerable to SQL injection if you are using a
 | 				 multi-byte character set and are running versions lower than these.
diff --git a/application/config/migration.php b/application/config/migration.php
new file mode 100644
index 0000000..dba8700
--- /dev/null
+++ b/application/config/migration.php
@@ -0,0 +1,42 @@
+<?php defined('BASEPATH') OR exit('No direct script access allowed');

+/*

+|--------------------------------------------------------------------------

+| Enable/Disable Migrations

+|--------------------------------------------------------------------------

+|

+| Migrations are disabled by default for security reasons.

+| You should enable migrations whenever you intend to do a schema migration

+| and disable it back when you're done.

+|

+*/

+$config['migration_enabled'] = FALSE;

+

+

+/*

+|--------------------------------------------------------------------------

+| Migrations version

+|--------------------------------------------------------------------------

+|

+| This is used to set migration version that the file system should be on.

+| If you run $this->migration->latest() this is the version that schema will

+| be upgraded / downgraded to.

+|

+*/

+$config['migration_version'] = 0;

+

+

+/*

+|--------------------------------------------------------------------------

+| Migrations Path

+|--------------------------------------------------------------------------

+|

+| Path to your migrations folder.

+| Typically, it will be within your application path.

+| Also, writing permission is required within the migrations path.

+|

+*/

+$config['migration_path'] = APPPATH . 'migrations/';

+

+

+/* End of file migration.php */

+/* Location: ./application/config/migration.php */
\ No newline at end of file
diff --git a/system/core/CodeIgniter.php b/system/core/CodeIgniter.php
index 94fecb5..a8895d6 100644
--- a/system/core/CodeIgniter.php
+++ b/system/core/CodeIgniter.php
@@ -32,7 +32,7 @@
  *  Define the CodeIgniter Version
  * ------------------------------------------------------
  */
-	define('CI_VERSION', '2.0.3');
+	define('CI_VERSION', '2.1.0-dev');
 
 /*
  * ------------------------------------------------------
@@ -267,7 +267,25 @@
 		OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller')))
 		)
 	{
-		show_404("{$class}/{$method}");
+		if ( ! empty($RTR->routes['404_override']))
+		{
+			$x = explode('/', $RTR->routes['404_override']);
+			$class = $x[0];
+			$method = (isset($x[1]) ? $x[1] : 'index');
+			if ( ! class_exists($class))
+			{
+				if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
+				{
+					show_404("{$class}/{$method}");
+				}
+
+				include_once(APPPATH.'controllers/'.$class.'.php');
+			}
+		}
+		else
+		{
+			show_404("{$class}/{$method}");
+		}
 	}
 
 /*
diff --git a/system/core/Controller.php b/system/core/Controller.php
index ec86b79..fddb81e 100644
--- a/system/core/Controller.php
+++ b/system/core/Controller.php
@@ -48,7 +48,7 @@
 
 		$this->load =& load_class('Loader', 'core');
 
-		$this->load->set_base_classes()->ci_autoloader();
+		$this->load->initialize();
 		
 		log_message('debug', "Controller Class Initialized");
 	}
diff --git a/system/core/Loader.php b/system/core/Loader.php
index 7c8b298..a0fbaa8 100644
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -62,17 +62,22 @@
 	// --------------------------------------------------------------------
 	
 	/**
-	 * Set _base_classes variable
+	 * Initialize the Loader
 	 *
 	 * This method is called once in CI_Controller.
 	 *
 	 * @param 	array 	
 	 * @return 	object
 	 */
-	public function set_base_classes()
+	public function initialize()
 	{
+		$this->_ci_classes = array();
+		$this->_ci_loaded_files = array();
+		$this->_ci_models = array();
 		$this->_base_classes =& is_loaded();
-		
+
+		$this->_ci_autoloader();
+
 		return $this;
 	}
 
@@ -935,22 +940,22 @@
 					// first, global next
 					if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'))
 					{
-						include_once($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
+						include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
 						break;
 					}
 					elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'))
 					{
-						include_once($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
+						include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
 						break;
 					}
 					elseif (file_exists($path .'config/'.strtolower($class).'.php'))
 					{
-						include_once($path .'config/'.strtolower($class).'.php');
+						include($path .'config/'.strtolower($class).'.php');
 						break;
 					}
 					elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php'))
 					{
-						include_once($path .'config/'.ucfirst(strtolower($class)).'.php');
+						include($path .'config/'.ucfirst(strtolower($class)).'.php');
 						break;
 					}
 				}
@@ -1020,23 +1025,19 @@
 	 * The config/autoload.php file contains an array that permits sub-systems,
 	 * libraries, and helpers to be loaded automatically.
 	 *
-	 * This function is public, as it's used in the CI_Controller class.  
-	 * However, there is no reason you should ever needs to use it.
-	 *
 	 * @param	array
 	 * @return	void
 	 */
-	public function ci_autoloader()
+	private function _ci_autoloader()
 	{
 		if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
 		{
-			include_once(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
+			include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
 		}
 		else
 		{
-			include_once(APPPATH.'config/autoload.php');
+			include(APPPATH.'config/autoload.php');
 		}
-		
 
 		if ( ! isset($autoload))
 		{
diff --git a/system/core/Router.php b/system/core/Router.php
index 5e92a04..668ac09 100644
--- a/system/core/Router.php
+++ b/system/core/Router.php
@@ -244,7 +244,20 @@
 				// Does the requested controller exist in the sub-folder?
 				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
 				{
-					show_404($this->fetch_directory().$segments[0]);
+					if ( ! empty($this->routes['404_override']))
+					{
+						$x = explode('/', $this->routes['404_override']);
+
+						$this->set_directory('');
+						$this->set_class($x[0]);
+						$this->set_method(isset($x[1]) ? $x[1] : 'index');
+					
+						return $x;
+					}
+					else
+					{
+						show_404($this->fetch_directory().$segments[0]);
+					}
 				}
 			}
 			else
diff --git a/system/database/DB_active_rec.php b/system/database/DB_active_rec.php
index 2af3553..7bab729 100644
--- a/system/database/DB_active_rec.php
+++ b/system/database/DB_active_rec.php
@@ -870,11 +870,11 @@
 	 */
 	public function limit($value, $offset = '')
 	{
-		$this->ar_limit = $value;
+		$this->ar_limit = (int) $value;
 
 		if ($offset != '')
 		{
-			$this->ar_offset = $offset;
+			$this->ar_offset = (int) $offset;
 		}
 
 		return $this;
@@ -1322,7 +1322,7 @@
 		{
 			if ($this->db_debug)
 			{
-				return $this->display_error('db_myst_use_index');
+				return $this->display_error('db_must_use_index');
 			}
 
 			return FALSE;
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 10e8ed0..f3e824d 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -218,7 +218,7 @@
 
 		// Some DBs have functions that return the version, and don't run special
 		// SQL queries per se. In these instances, just return the result.
-		$driver_version_exceptions = array('oci8', 'sqlite');
+		$driver_version_exceptions = array('oci8', 'sqlite', 'cubrid');
 
 		if (in_array($this->dbdriver, $driver_version_exceptions))
 		{
diff --git a/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php
new file mode 100644
index 0000000..3f01092
--- /dev/null
+++ b/system/database/drivers/cubrid/cubrid_driver.php
@@ -0,0 +1,791 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		Esen Sagynov
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 2.0.2
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * CUBRID Database Adapter Class
+ *
+ * Note: _DB is an extender class that the app controller
+ * creates dynamically based on whether the active record
+ * class is being used or not.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Drivers
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_driver extends CI_DB {
+
+	// Default CUBRID Broker port. Will be used unless user
+	// explicitly specifies another one.
+	const DEFAULT_PORT = 33000;
+
+	var $dbdriver = 'cubrid';
+
+	// The character used for escaping - no need in CUBRID
+	var	$_escape_char = '';
+
+	// clause and character used for LIKE escape sequences - not used in CUBRID
+	var $_like_escape_str = '';
+	var $_like_escape_chr = '';
+
+	/**
+	 * The syntax to count rows is slightly different across different
+	 * database engines, so this string appears in each driver and is
+	 * used for the count_all() and count_all_results() functions.
+	 */
+	var $_count_string = 'SELECT COUNT(*) AS ';
+	var $_random_keyword = ' RAND()'; // database specific random keyword
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_connect()
+	{
+		// If no port is defined by the user, use the default value
+		if ($this->port == '')
+		{
+			$this->port = self::DEFAULT_PORT;
+		}
+
+		$conn = cubrid_connect($this->hostname, $this->port, $this->database, $this->username, $this->password);
+
+		if ($conn)
+		{
+			// Check if a user wants to run queries in dry, i.e. run the
+			// queries but not commit them.
+			if (isset($this->auto_commit) && ! $this->auto_commit)
+			{
+				cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_FALSE);
+			}
+			else
+			{
+				cubrid_set_autocommit($conn, CUBRID_AUTOCOMMIT_TRUE);
+				$this->auto_commit = TRUE;
+			}
+		}
+
+		return $conn;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Persistent database connection
+	 * In CUBRID persistent DB connection is supported natively in CUBRID
+	 * engine which can be configured in the CUBRID Broker configuration
+	 * file by setting the CCI_PCONNECT parameter to ON. In that case, all
+	 * connections established between the client application and the
+	 * server will become persistent. This is calling the same
+	 * @cubrid_connect function will establish persisten connection
+	 * considering that the CCI_PCONNECT is ON.
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_pconnect()
+	{
+		return $this->db_connect();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Reconnect
+	 *
+	 * Keep / reestablish the db connection if no queries have been
+	 * sent for a length of time exceeding the server's idle timeout
+	 *
+	 * @access	public
+	 * @return	void
+	 */
+	function reconnect()
+	{
+		if (cubrid_ping($this->conn_id) === FALSE)
+		{
+			$this->conn_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Select the database
+	 *
+	 * @access	private called by the base class
+	 * @return	resource
+	 */
+	function db_select()
+	{
+		// In CUBRID there is no need to select a database as the database
+		// is chosen at the connection time.
+		// So, to determine if the database is "selected", all we have to
+		// do is ping the server and return that value.
+		return cubrid_ping($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set client character set
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	string
+	 * @return	resource
+	 */
+	function db_set_charset($charset, $collation)
+	{
+		// In CUBRID, there is no need to set charset or collation.
+		// This is why returning true will allow the application continue
+		// its normal process.
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Version number query string
+	 *
+	 * @access	public
+	 * @return	string
+	 */
+	function _version()
+	{
+		// To obtain the CUBRID Server version, no need to run the SQL query.
+		// CUBRID PHP API provides a function to determin this value.
+		// This is why we also need to add 'cubrid' value to the list of
+		// $driver_version_exceptions array in DB_driver class in
+		// version() function.
+		return cubrid_get_server_info($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @access	private called by the base class
+	 * @param	string	an SQL query
+	 * @return	resource
+	 */
+	function _execute($sql)
+	{
+		$sql = $this->_prep_query($sql);
+		return @cubrid_query($sql, $this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Prep the query
+	 *
+	 * If needed, each database adapter can prep the query string
+	 *
+	 * @access	private called by execute()
+	 * @param	string	an SQL query
+	 * @return	string
+	 */
+	function _prep_query($sql)
+	{
+		// No need to prepare
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_begin($test_mode = FALSE)
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		// Reset the transaction failure flag.
+		// If the $test_mode flag is set to TRUE transactions will be rolled back
+		// even if the queries produce a successful result.
+		$this->_trans_failure = ($test_mode === TRUE) ? TRUE : FALSE;
+
+		if (cubrid_get_autocommit($this->conn_id))
+		{
+			cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_commit()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		cubrid_commit($this->conn_id);
+
+		if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
+		{
+			cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @access	public
+	 * @return	bool
+	 */
+	function trans_rollback()
+	{
+		if ( ! $this->trans_enabled)
+		{
+			return TRUE;
+		}
+
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ($this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		cubrid_rollback($this->conn_id);
+
+		if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id))
+		{
+			cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE);
+		}
+
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape String
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @return	string
+	 */
+	function escape_str($str, $like = FALSE)
+	{
+		if (is_array($str))
+		{
+			foreach ($str as $key => $val)
+			{
+				$str[$key] = $this->escape_str($val, $like);
+			}
+
+			return $str;
+		}
+
+		if (function_exists('cubrid_real_escape_string') AND is_resource($this->conn_id))
+		{
+			$str = cubrid_real_escape_string($str, $this->conn_id);
+		}
+		else
+		{
+			$str = addslashes($str);
+		}
+
+		// escape LIKE condition wildcards
+		if ($like === TRUE)
+		{
+			$str = str_replace(array('%', '_'), array('\\%', '\\_'), $str);
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function affected_rows()
+	{
+		return @cubrid_affected_rows($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function insert_id()
+	{
+		return @cubrid_insert_id($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Count All" query
+	 *
+	 * Generates a platform-specific query string that counts all records in
+	 * the specified table
+	 *
+	 * @access	public
+	 * @param	string
+	 * @return	string
+	 */
+	function count_all($table = '')
+	{
+		if ($table == '')
+		{
+			return 0;
+		}
+		
+		$query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE));
+
+		if ($query->num_rows() == 0)
+		{
+			return 0;
+		}
+
+		$row = $query->row();
+		return (int) $row->numrows;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * List table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @access	private
+	 * @param	boolean
+	 * @return	string
+	 */
+	function _list_tables($prefix_limit = FALSE)
+	{
+		$sql = "SHOW TABLES";
+
+		if ($prefix_limit !== FALSE AND $this->dbprefix != '')
+		{
+			$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%'";
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	function _list_columns($table = '')
+	{
+		return "SHOW COLUMNS FROM ".$this->_protect_identifiers($table, TRUE, NULL, FALSE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data query
+	 *
+	 * Generates a platform-specific query so that the column data can be retrieved
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @return	object
+	 */
+	function _field_data($table)
+	{
+		return "SELECT * FROM ".$table." LIMIT 1";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message string
+	 *
+	 * @access	private
+	 * @return	string
+	 */
+	function _error_message()
+	{
+		return cubrid_error($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message number
+	 *
+	 * @access	private
+	 * @return	integer
+	 */
+	function _error_number()
+	{
+		return cubrid_errno($this->conn_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape the SQL Identifiers
+	 *
+	 * This function escapes column and table names
+	 *
+	 * @access	private
+	 * @param	string
+	 * @return	string
+	 */
+	function _escape_identifiers($item)
+	{
+		if ($this->_escape_char == '')
+		{
+			return $item;
+		}
+
+		foreach ($this->_reserved_identifiers as $id)
+		{
+			if (strpos($item, '.'.$id) !== FALSE)
+			{
+				$str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item);
+
+				// remove duplicates if the user already included the escape
+				return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
+			}
+		}
+
+		if (strpos($item, '.') !== FALSE)
+		{
+			$str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char;
+		}
+		else
+		{
+			$str = $this->_escape_char.$item.$this->_escape_char;
+		}
+
+		// remove duplicates if the user already included the escape
+		return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * From Tables
+	 *
+	 * This function implicitly groups FROM tables so there is no confusion
+	 * about operator precedence in harmony with SQL standards
+	 *
+	 * @access	public
+	 * @param	type
+	 * @return	type
+	 */
+	function _from_tables($tables)
+	{
+		if ( ! is_array($tables))
+		{
+			$tables = array($tables);
+		}
+
+		return '('.implode(', ', $tables).')';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	function _insert($table, $keys, $values)
+	{
+		return "INSERT INTO ".$table." (\"".implode('", "', $keys)."\") VALUES (".implode(', ', $values).")";
+	}
+
+	// --------------------------------------------------------------------
+
+
+	/**
+	 * Replace statement
+	 *
+	 * Generates a platform-specific replace string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	function _replace($table, $keys, $values)
+	{
+		return "REPLACE INTO ".$table." (\"".implode('", "', $keys)."\") VALUES (".implode(', ', $values).")";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert_batch statement
+	 *
+	 * Generates a platform-specific insert string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	function _insert_batch($table, $keys, $values)
+	{
+		return "INSERT INTO ".$table." (\"".implode('", "', $keys)."\") VALUES ".implode(', ', $values);
+	}
+
+	// --------------------------------------------------------------------
+
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the update data
+	 * @param	array	the where clause
+	 * @param	array	the orderby clause
+	 * @param	array	the limit clause
+	 * @return	string
+	 */
+	function _update($table, $values, $where, $orderby = array(), $limit = FALSE)
+	{
+		foreach ($values as $key => $val)
+		{
+			$valstr[] = sprintf('"%s" = %s', $key, $val);
+		}
+
+		$limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
+
+		$orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):'';
+
+		$sql = "UPDATE ".$table." SET ".implode(', ', $valstr);
+
+		$sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : '';
+
+		$sql .= $orderby.$limit;
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+
+	/**
+	 * Update_Batch statement
+	 *
+	 * Generates a platform-specific batch update string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the update data
+	 * @param	array	the where clause
+	 * @return	string
+	 */
+	function _update_batch($table, $values, $index, $where = NULL)
+	{
+		$ids = array();
+		$where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : '';
+
+		foreach ($values as $key => $val)
+		{
+			$ids[] = $val[$index];
+
+			foreach (array_keys($val) as $field)
+			{
+				if ($field != $index)
+				{
+					$final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
+				}
+			}
+		}
+
+		$sql = "UPDATE ".$table." SET ";
+		$cases = '';
+
+		foreach ($final as $k => $v)
+		{
+			$cases .= $k.' = CASE '."\n";
+			foreach ($v as $row)
+			{
+				$cases .= $row."\n";
+			}
+
+			$cases .= 'ELSE '.$k.' END, ';
+		}
+
+		$sql .= substr($cases, 0, -2);
+
+		$sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 * If the database does not support the truncate() command
+	 * This function maps to "DELETE FROM table"
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	function _truncate($table)
+	{
+		return "TRUNCATE ".$table;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @access	public
+	 * @param	string	the table name
+	 * @param	array	the where clause
+	 * @param	string	the limit clause
+	 * @return	string
+	 */
+	function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	{
+		$conditions = '';
+
+		if (count($where) > 0 OR count($like) > 0)
+		{
+			$conditions = "\nWHERE ";
+			$conditions .= implode("\n", $this->ar_where);
+
+			if (count($where) > 0 && count($like) > 0)
+			{
+				$conditions .= " AND ";
+			}
+			$conditions .= implode("\n", $like);
+		}
+
+		$limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
+
+		return "DELETE FROM ".$table.$conditions.$limit;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Limit string
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @access	public
+	 * @param	string	the sql query string
+	 * @param	integer	the number of rows to limit the query to
+	 * @param	integer	the offset value
+	 * @return	string
+	 */
+	function _limit($sql, $limit, $offset)
+	{
+		if ($offset == 0)
+		{
+			$offset = '';
+		}
+		else
+		{
+			$offset .= ", ";
+		}
+
+		return $sql."LIMIT ".$offset.$limit;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @access	public
+	 * @param	resource
+	 * @return	void
+	 */
+	function _close($conn_id)
+	{
+		@cubrid_close($conn_id);
+	}
+
+}
+
+
+/* End of file cubrid_driver.php */
+/* Location: ./system/database/drivers/cubrid/cubrid_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php
new file mode 100644
index 0000000..bab03f7
--- /dev/null
+++ b/system/database/drivers/cubrid/cubrid_forge.php
@@ -0,0 +1,288 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		Esen Sagynov
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * CUBRID Forge Class
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_forge extends CI_DB_forge {
+
+	/**
+	 * Create database
+	 *
+	 * @access	private
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	function _create_database($name)
+	{
+		// CUBRID does not allow to create a database in SQL. The GUI tools
+		// have to be used for this purpose.
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @access	private
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	function _drop_database($name)
+	{
+		// CUBRID does not allow to drop a database in SQL. The GUI tools
+		// have to be used for this purpose.
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Process Fields
+	 *
+	 * @access	private
+	 * @param	mixed	the fields
+	 * @return	string
+	 */
+	function _process_fields($fields)
+	{
+		$current_field_count = 0;
+		$sql = '';
+
+		foreach ($fields as $field=>$attributes)
+		{
+			// Numeric field names aren't allowed in databases, so if the key is
+			// numeric, we know it was assigned by PHP and the developer manually
+			// entered the field information, so we'll simply add it to the list
+			if (is_numeric($field))
+			{
+				$sql .= "\n\t$attributes";
+			}
+			else
+			{
+				$attributes = array_change_key_case($attributes, CASE_UPPER);
+
+				$sql .= "\n\t\"" . $this->db->_protect_identifiers($field) . "\"";
+
+				if (array_key_exists('NAME', $attributes))
+				{
+					$sql .= ' '.$this->db->_protect_identifiers($attributes['NAME']).' ';
+				}
+
+				if (array_key_exists('TYPE', $attributes))
+				{
+					$sql .= ' '.$attributes['TYPE'];
+
+					if (array_key_exists('CONSTRAINT', $attributes))
+					{
+						switch ($attributes['TYPE'])
+						{
+							case 'decimal':
+							case 'float':
+							case 'numeric':
+								$sql .= '('.implode(',', $attributes['CONSTRAINT']).')';
+								break;
+							case 'enum': 	// As of version 8.4.0 CUBRID does not support
+											// enum data type.
+											break;
+							case 'set':
+								$sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")';
+								break;
+							default:
+								$sql .= '('.$attributes['CONSTRAINT'].')';
+						}
+					}
+				}
+
+				if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE)
+				{
+					//$sql .= ' UNSIGNED';
+					// As of version 8.4.0 CUBRID does not support UNSIGNED INTEGER data type.
+					// Will be supported in the next release as a part of MySQL Compatibility.
+				}
+
+				if (array_key_exists('DEFAULT', $attributes))
+				{
+					$sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\'';
+				}
+
+				if (array_key_exists('NULL', $attributes) && $attributes['NULL'] === TRUE)
+				{
+					$sql .= ' NULL';
+				}
+				else
+				{
+					$sql .= ' NOT NULL';
+				}
+
+				if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE)
+				{
+					$sql .= ' AUTO_INCREMENT';
+				}
+
+				if (array_key_exists('UNIQUE', $attributes) && $attributes['UNIQUE'] === TRUE)
+				{
+					$sql .= ' UNIQUE';
+				}
+			}
+
+			// don't add a comma on the end of the last field
+			if (++$current_field_count < count($fields))
+			{
+				$sql .= ',';
+			}
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create Table
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @param	mixed	the fields
+	 * @param	mixed	primary key(s)
+	 * @param	mixed	key(s)
+	 * @param	boolean	should 'IF NOT EXISTS' be added to the SQL
+	 * @return	bool
+	 */
+	function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	{
+		$sql = 'CREATE TABLE ';
+
+		if ($if_not_exists === TRUE)
+		{
+			//$sql .= 'IF NOT EXISTS ';
+			// As of version 8.4.0 CUBRID does not support this SQL syntax.
+		}
+
+		$sql .= $this->db->_escape_identifiers($table)." (";
+
+		$sql .= $this->_process_fields($fields);
+
+		// If there is a PK defined
+		if (count($primary_keys) > 0)
+		{
+			$key_name = "pk_" . $table . "_" .
+				$this->db->_protect_identifiers(implode('_', $primary_keys));
+			
+			$primary_keys = $this->db->_protect_identifiers($primary_keys);
+			$sql .= ",\n\tCONSTRAINT " . $key_name . " PRIMARY KEY(" . implode(', ', $primary_keys) . ")";
+		}
+
+		if (is_array($keys) && count($keys) > 0)
+		{
+			foreach ($keys as $key)
+			{
+				if (is_array($key))
+				{
+					$key_name = $this->db->_protect_identifiers(implode('_', $key));
+					$key = $this->db->_protect_identifiers($key);
+				}
+				else
+				{
+					$key_name = $this->db->_protect_identifiers($key);
+					$key = array($key_name);
+				}
+				
+				$sql .= ",\n\tKEY \"{$key_name}\" (" . implode(', ', $key) . ")";
+			}
+		}
+
+		$sql .= "\n);";
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * @access	private
+	 * @return	string
+	 */
+	function _drop_table($table)
+	{
+		return "DROP TABLE IF EXISTS ".$this->db->_escape_identifiers($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Alter table query
+	 *
+	 * Generates a platform-specific query so that a table can be altered
+	 * Called by add_column(), drop_column(), and column_alter(),
+	 *
+	 * @access	private
+	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
+	 * @param	string	the column name
+	 * @param	array	fields
+	 * @param	string	the field after which we should add the new field
+	 * @return	object
+	 */
+	function _alter_table($alter_type, $table, $fields, $after_field = '')
+	{
+		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ";
+
+		// DROP has everything it needs now.
+		if ($alter_type == 'DROP')
+		{
+			return $sql.$this->db->_protect_identifiers($fields);
+		}
+
+		$sql .= $this->_process_fields($fields);
+
+		if ($after_field != '')
+		{
+			$sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field);
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rename a table
+	 *
+	 * Generates a platform-specific query so that a table can be renamed
+	 *
+	 * @access	private
+	 * @param	string	the old table name
+	 * @param	string	the new table name
+	 * @return	string
+	 */
+	function _rename_table($table_name, $new_table_name)
+	{
+		$sql = 'RENAME TABLE '.$this->db->_protect_identifiers($table_name)." AS ".$this->db->_protect_identifiers($new_table_name);
+		return $sql;
+	}
+
+}
+
+/* End of file cubrid_forge.php */
+/* Location: ./system/database/drivers/cubrid/cubrid_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/cubrid/cubrid_result.php b/system/database/drivers/cubrid/cubrid_result.php
new file mode 100644
index 0000000..6f0c2b5
--- /dev/null
+++ b/system/database/drivers/cubrid/cubrid_result.php
@@ -0,0 +1,202 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		Esen Sagynov
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 2.0.2
+ * @filesource
+ */
+
+// --------------------------------------------------------------------
+
+/**
+ * CUBRID Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_result extends CI_DB_result {
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function num_rows()
+	{
+		return @cubrid_num_rows($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @access	public
+	 * @return	integer
+	 */
+	function num_fields()
+	{
+		return @cubrid_num_fields($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @access	public
+	 * @return	array
+	 */
+	function list_fields()
+	{
+		return cubrid_column_names($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @access	public
+	 * @return	array
+	 */
+	function field_data()
+	{
+		$retval = array();
+
+		$tablePrimaryKeys = array();
+
+		while ($field = cubrid_fetch_field($this->result_id))
+		{
+			$F				= new stdClass();
+			$F->name		= $field->name;
+			$F->type		= $field->type;
+			$F->default		= $field->def;
+			$F->max_length	= $field->max_length;
+
+			// At this moment primary_key property is not returned when
+			// cubrid_fetch_field is called. The following code will
+			// provide a patch for it. primary_key property will be added
+			// in the next release.
+
+			// TODO: later version of CUBRID will provide primary_key
+			// property.
+			// When PK is defined in CUBRID, an index is automatically
+			// created in the db_index system table in the form of
+			// pk_tblname_fieldname. So the following will count how many
+			// columns are there which satisfy this format.
+			// The query will search for exact single columns, thus
+			// compound PK is not supported.
+			$res = cubrid_query($this->conn_id,
+				"SELECT COUNT(*) FROM db_index WHERE class_name = '" . $field->table .
+				"' AND is_primary_key = 'YES' AND index_name = 'pk_" .
+				$field->table . "_" . $field->name . "'"
+			);
+
+			if ($res)
+			{
+				$row = cubrid_fetch_array($res, CUBRID_NUM);
+				$F->primary_key = ($row[0] > 0 ? 1 : null);
+			}
+			else
+			{
+				$F->primary_key = null;
+			}
+
+			if (is_resource($res))
+			{
+				cubrid_close_request($res);
+				$this->result_id = FALSE;
+			}
+
+			$retval[] = $F;
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	null
+	 */
+	function free_result()
+	{
+		if(is_resource($this->result_id) ||
+			get_resource_type($this->result_id) == "Unknown" &&
+			preg_match('/Resource id #/', strval($this->result_id)))
+		{
+			cubrid_close_request($this->result_id);
+			$this->result_id = FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Data Seek
+	 *
+	 * Moves the internal pointer to the desired offset. We call
+	 * this internally before fetching results to make sure the
+	 * result set starts at zero
+	 *
+	 * @access	private
+	 * @return	array
+	 */
+	function _data_seek($n = 0)
+	{
+		return cubrid_data_seek($this->result_id, $n);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @access	private
+	 * @return	array
+	 */
+	function _fetch_assoc()
+	{
+		return cubrid_fetch_assoc($this->result_id);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @access	private
+	 * @return	object
+	 */
+	function _fetch_object()
+	{
+		return cubrid_fetch_object($this->result_id);
+	}
+
+}
+
+
+/* End of file cubrid_result.php */
+/* Location: ./system/database/drivers/cubrid/cubrid_result.php */
\ No newline at end of file
diff --git a/system/database/drivers/cubrid/cubrid_utility.php b/system/database/drivers/cubrid/cubrid_utility.php
new file mode 100644
index 0000000..cd16d1e
--- /dev/null
+++ b/system/database/drivers/cubrid/cubrid_utility.php
@@ -0,0 +1,108 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		Esen Sagynov
+ * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * CUBRID Utility Class
+ *
+ * @category	Database
+ * @author		Esen Sagynov
+ * @link		http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_cubrid_utility extends CI_DB_utility {
+
+	/**
+	 * List databases
+	 *
+	 * @access	private
+	 * @return	array
+	 */
+	function _list_databases()
+	{
+		// CUBRID does not allow to see the list of all databases on the
+		// server. It is the way its architecture is designed. Every
+		// database is independent and isolated.
+		// For this reason we can return only the name of the currect
+		// connected database.
+		if ($this->conn_id)
+		{
+			return "SELECT '" . $this->database . "'";
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Optimize table query
+	 *
+	 * Generates a platform-specific query so that a table can be optimized
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	object
+	 * @link 	http://www.cubrid.org/manual/840/en/Optimize%20Database
+	 */
+	function _optimize_table($table)
+	{
+		// No SQL based support in CUBRID as of version 8.4.0. Database or
+		// table optimization can be performed using CUBRID Manager
+		// database administration tool. See the link above for more info.
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Repair table query
+	 *
+	 * Generates a platform-specific query so that a table can be repaired
+	 *
+	 * @access	private
+	 * @param	string	the table name
+	 * @return	object
+	 * @link 	http://www.cubrid.org/manual/840/en/Checking%20Database%20Consistency
+	 */
+	function _repair_table($table)
+	{
+		// Not supported in CUBRID as of version 8.4.0. Database or
+		// table consistency can be checked using CUBRID Manager
+		// database administration tool. See the link above for more info.
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+	/**
+	 * CUBRID Export
+	 *
+	 * @access	private
+	 * @param	array	Preferences
+	 * @return	mixed
+	 */
+	function _backup($params = array())
+	{
+		// No SQL based support in CUBRID as of version 8.4.0. Database or
+		// table backup can be performed using CUBRID Manager
+		// database administration tool.
+		return $this->db->display_error('db_unsuported_feature');
+	}
+}
+
+/* End of file cubrid_utility.php */
+/* Location: ./system/database/drivers/cubrid/cubrid_utility.php */
\ No newline at end of file
diff --git a/system/database/drivers/cubrid/index.html b/system/database/drivers/cubrid/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/database/drivers/cubrid/index.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+	<title>403 Forbidden</title>
+</head>
+<body>
+
+<p>Directory access is forbidden.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php
index b7d547c..28c75a5 100644
--- a/system/database/drivers/mysql/mysql_driver.php
+++ b/system/database/drivers/mysql/mysql_driver.php
@@ -54,6 +54,9 @@
 	var $_count_string = 'SELECT COUNT(*) AS ';
 	var $_random_keyword = ' RAND()'; // database specific random keyword
 
+	// whether SET NAMES must be used to set the character set
+	var $use_set_names;
+	
 	/**
 	 * Non-persistent database connection
 	 *
@@ -132,15 +135,13 @@
 	 */
 	function db_set_charset($charset, $collation)
 	{
-		static $use_set_names;
-		
-		if ( ! isset($use_set_names))
+		if ( ! isset($this->use_set_names))
 		{
 			// mysql_set_charset() requires PHP >= 5.2.3 and MySQL >= 5.0.7, use SET NAMES as fallback
-			$use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysql_get_server_info(), '5.0.7', '>=')) ? FALSE : TRUE;
+			$this->use_set_names = (version_compare(PHP_VERSION, '5.2.3', '>=') && version_compare(mysql_get_server_info(), '5.0.7', '>=')) ? FALSE : TRUE;
 		}
 
-		if ($use_set_names)
+		if ($this->use_set_names === TRUE)
 		{
 			return @mysql_query("SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'", $this->conn_id);
 		}
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index b8586c2..b1796c9 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -54,6 +54,9 @@
 	 */
 	var $delete_hack = TRUE;
 
+	// whether SET NAMES must be used to set the character set
+	var $use_set_names;
+	
 	// --------------------------------------------------------------------
 
 	/**
@@ -132,15 +135,13 @@
 	 */
 	function _db_set_charset($charset, $collation)
 	{
-		static $use_set_names;
-		
-		if ( ! isset($use_set_names))
+		if ( ! isset($this->use_set_names))
 		{
 			// mysqli_set_charset() requires MySQL >= 5.0.7, use SET NAMES as fallback
-			$use_set_names = (version_compare(mysqli_get_server_info($this->conn_id), '5.0.7', '>=')) ? FALSE : TRUE;
+			$this->use_set_names = (version_compare(mysqli_get_server_info($this->conn_id), '5.0.7', '>=')) ? FALSE : TRUE;
 		}
 
-		if ($use_set_names)
+		if ($this->use_set_names === TRUE)
 		{
 			return @mysqli_query($this->conn_id, "SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'");
 		}
diff --git a/system/helpers/form_helper.php b/system/helpers/form_helper.php
index 2925d3c..47f93e7 100644
--- a/system/helpers/form_helper.php
+++ b/system/helpers/form_helper.php
@@ -64,8 +64,8 @@
 
 		$form .= '>';
 
-		// CSRF
-		if ($CI->config->item('csrf_protection') === TRUE)
+		// Add CSRF field if enabled, but leave it out for GET requests and requests to external websites	
+		if ($CI->config->item('csrf_protection') === TRUE AND ! (strpos($action, $CI->config->site_url()) === FALSE OR strpos($form, 'method="get"')))	
 		{
 			$hidden[$CI->security->get_csrf_token_name()] = $CI->security->get_csrf_hash();
 		}
@@ -249,7 +249,7 @@
 {
 	function form_textarea($data = '', $value = '', $extra = '')
 	{
-		$defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '90', 'rows' => '12');
+		$defaults = array('name' => (( ! is_array($data)) ? $data : ''), 'cols' => '40', 'rows' => '10');
 
 		if ( ! is_array($data) OR ! isset($data['value']))
 		{
diff --git a/system/language/english/form_validation_lang.php b/system/language/english/form_validation_lang.php
index 3f24090..3418f29 100644
--- a/system/language/english/form_validation_lang.php
+++ b/system/language/english/form_validation_lang.php
@@ -17,6 +17,7 @@
 $lang['integer']			= "The %s field must contain an integer.";
 $lang['regex_match']		= "The %s field is not in the correct format.";
 $lang['matches']			= "The %s field does not match the %s field.";
+$lang['is_unique'] 			= "The %s field must contain a unique value.";
 $lang['is_natural']			= "The %s field must contain only positive numbers.";
 $lang['is_natural_no_zero']	= "The %s field must contain a number greater than zero.";
 $lang['decimal']			= "The %s field must contain a decimal number.";
diff --git a/system/language/english/migration_lang.php b/system/language/english/migration_lang.php
new file mode 100644
index 0000000..4763ca2
--- /dev/null
+++ b/system/language/english/migration_lang.php
@@ -0,0 +1,13 @@
+<?php
+
+$lang['migration_none_found']			= "No migrations were found.";
+$lang['migration_not_found']			= "This migration could not be found.";
+$lang['migration_multiple_version']		= "This are multiple migrations with the same version number: %d.";
+$lang['migration_class_doesnt_exist']	= "The migration class \"%s\" could not be found.";
+$lang['migration_missing_up_method']	= "The migration class \"%s\" is missing an 'up' method.";
+$lang['migration_missing_down_method']	= "The migration class \"%s\" is missing an 'up' method.";
+$lang['migration_invalid_filename']		= "Migration \"%s\" has an invalid filename.";
+
+
+/* End of file migration_lang.php */
+/* Location: ./system/language/english/migration_lang.php */
\ No newline at end of file
diff --git a/system/libraries/Cart.php b/system/libraries/Cart.php
index b2eaa9a..ab5a70c 100644
--- a/system/libraries/Cart.php
+++ b/system/libraries/Cart.php
@@ -99,7 +99,7 @@
 		$save_cart = FALSE;
 		if (isset($items['id']))
 		{
-			if ($this->_insert($items) == TRUE)
+			if (($rowid = $this->_insert($items)))
 			{
 				$save_cart = TRUE;
 			}
@@ -110,7 +110,7 @@
 			{
 				if (is_array($val) AND isset($val['id']))
 				{
-					if ($this->_insert($val) == TRUE)
+					if ($this->_insert($val))
 					{
 						$save_cart = TRUE;
 					}
@@ -122,7 +122,7 @@
 		if ($save_cart == TRUE)
 		{
 			$this->_save_cart();
-			return TRUE;
+			return isset($rowid) ? $rowid : TRUE;
 		}
 
 		return FALSE;
@@ -244,7 +244,7 @@
 		}
 
 		// Woot!
-		return TRUE;
+		return $rowid;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 03eccea..fb9a750 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -405,12 +405,12 @@
 	/**
 	 * Add a Header Item
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @param	string
 	 * @return	void
 	 */
-	private function _set_header($header, $value)
+	protected function _set_header($header, $value)
 	{
 		$this->_headers[$header] = $value;
 	}
@@ -420,11 +420,11 @@
 	/**
 	 * Convert a String to an Array
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @return	array
 	 */
-	private function _str_to_array($email)
+	protected function _str_to_array($email)
 	{
 		if ( ! is_array($email))
 		{
@@ -577,10 +577,10 @@
 	/**
 	 * Set Message Boundary
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	void
 	 */
-	private function _set_boundaries()
+	protected function _set_boundaries()
 	{
 		$this->_alt_boundary = "B_ALT_".uniqid(''); // multipart/alternative
 		$this->_atc_boundary = "B_ATC_".uniqid(''); // attachment boundary
@@ -591,10 +591,10 @@
 	/**
 	 * Get the Message ID
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_message_id()
+	protected function _get_message_id()
 	{
 		$from = $this->_headers['Return-Path'];
 		$from = str_replace(">", "", $from);
@@ -608,11 +608,11 @@
 	/**
 	 * Get Mail Protocol
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	bool
 	 * @return	string
 	 */
-	private function _get_protocol($return = TRUE)
+	protected function _get_protocol($return = TRUE)
 	{
 		$this->protocol = strtolower($this->protocol);
 		$this->protocol = ( ! in_array($this->protocol, $this->_protocols, TRUE)) ? 'mail' : $this->protocol;
@@ -628,11 +628,11 @@
 	/**
 	 * Get Mail Encoding
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	bool
 	 * @return	string
 	 */
-	private function _get_encoding($return = TRUE)
+	protected function _get_encoding($return = TRUE)
 	{
 		$this->_encoding = ( ! in_array($this->_encoding, $this->_bit_depths)) ? '8bit' : $this->_encoding;
 
@@ -655,10 +655,10 @@
 	/**
 	 * Get content type (text/html/attachment)
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_content_type()
+	protected function _get_content_type()
 	{
 		if	($this->mailtype == 'html' &&  count($this->_attach_name) == 0)
 		{
@@ -683,10 +683,10 @@
 	/**
 	 * Set RFC 822 Date
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _set_date()
+	protected function _set_date()
 	{
 		$timezone = date("Z");
 		$operator = (strncmp($timezone, '-', 1) == 0) ? '-' : '+';
@@ -701,10 +701,10 @@
 	/**
 	 * Mime message
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_mime_message()
+	protected function _get_mime_message()
 	{
 		return "This is a multi-part message in MIME format.".$this->newline."Your email application may not support this format.";
 	}
@@ -802,10 +802,10 @@
 	 * If the user hasn't specified his own alternative message
 	 * it creates one by stripping the HTML
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_alt_message()
+	protected function _get_alt_message()
 	{
 		if ($this->alt_message != "")
 		{
@@ -941,11 +941,11 @@
 	/**
 	 * Build final headers
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @return	string
 	 */
-	private function _build_headers()
+	protected function _build_headers()
 	{
 		$this->_set_header('X-Sender', $this->clean_email($this->_headers['From']));
 		$this->_set_header('X-Mailer', $this->useragent);
@@ -959,10 +959,10 @@
 	/**
 	 * Write Headers as a string
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	void
 	 */
-	private function _write_headers()
+	protected function _write_headers()
 	{
 		if ($this->protocol == 'mail')
 		{
@@ -994,10 +994,10 @@
 	/**
 	 * Build Final Body and attachments
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	void
 	 */
-	private function _build_message()
+	protected function _build_message()
 	{
 		if ($this->wordwrap === TRUE  AND  $this->mailtype != 'html')
 		{
@@ -1177,12 +1177,12 @@
 	 * Prepares string for Quoted-Printable Content-Transfer-Encoding
 	 * Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @param	integer
 	 * @return	string
 	 */
-	private function _prep_quoted_printable($str, $charlim = '')
+	protected function _prep_quoted_printable($str, $charlim = '')
 	{
 		// Set the character limit
 		// Don't allow over 76, as that will make servers and MUAs barf
@@ -1275,7 +1275,7 @@
 	 * @param	bool	// set to TRUE for processing From: headers
 	 * @return	str
 	 */
-	private function _prep_q_encoding($str, $from = FALSE)
+	protected function _prep_q_encoding($str, $from = FALSE)
 	{
 		$str = str_replace(array("\r", "\n"), array('', ''), $str);
 
@@ -1440,10 +1440,10 @@
 	/**
 	 * Unwrap special elements
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	void
 	 */
-	private function _unwrap_specials()
+	protected function _unwrap_specials()
 	{
 		$this->_finalbody = preg_replace_callback("/\{unwrap\}(.*?)\{\/unwrap\}/si", array($this, '_remove_nl_callback'), $this->_finalbody);
 	}
@@ -1453,10 +1453,10 @@
 	/**
 	 * Strip line-breaks via callback
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _remove_nl_callback($matches)
+	protected function _remove_nl_callback($matches)
 	{
 		if (strpos($matches[1], "\r") !== FALSE OR strpos($matches[1], "\n") !== FALSE)
 		{
@@ -1471,10 +1471,10 @@
 	/**
 	 * Spool mail to the mail server
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _spool_email()
+	protected function _spool_email()
 	{
 		$this->_unwrap_specials();
 
@@ -1516,10 +1516,10 @@
 	/**
 	 * Send using mail()
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _send_with_mail()
+	protected function _send_with_mail()
 	{
 		if ($this->_safe_mode == TRUE)
 		{
@@ -1553,10 +1553,10 @@
 	/**
 	 * Send using Sendmail
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _send_with_sendmail()
+	protected function _send_with_sendmail()
 	{
 		$fp = @popen($this->mailpath . " -oi -f ".$this->clean_email($this->_headers['From'])." -t", 'w');
 
@@ -1591,10 +1591,10 @@
 	/**
 	 * Send using SMTP
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _send_with_smtp()
+	protected function _send_with_smtp()
 	{
 		if ($this->smtp_host == '')
 		{
@@ -1660,11 +1660,11 @@
 	/**
 	 * SMTP Connect
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @return	string
 	 */
-	private function _smtp_connect()
+	protected function _smtp_connect()
 	{
 		$this->_smtp_connect = fsockopen($this->smtp_host,
 										$this->smtp_port,
@@ -1687,12 +1687,12 @@
 	/**
 	 * Send SMTP command
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @param	string
 	 * @return	string
 	 */
-	private function _send_command($cmd, $data = '')
+	protected function _send_command($cmd, $data = '')
 	{
 		switch ($cmd)
 		{
@@ -1754,10 +1754,10 @@
 	/**
 	 *  SMTP Authenticate
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _smtp_authenticate()
+	protected function _smtp_authenticate()
 	{
 		if ( ! $this->_smtp_auth)
 		{
@@ -1808,10 +1808,10 @@
 	/**
 	 * Send SMTP data
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	bool
 	 */
-	private function _send_data($data)
+	protected function _send_data($data)
 	{
 		if ( ! fwrite($this->_smtp_connect, $data . $this->newline))
 		{
@@ -1829,10 +1829,10 @@
 	/**
 	 * Get SMTP data
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_smtp_data()
+	protected function _get_smtp_data()
 	{
 		$data = "";
 
@@ -1854,10 +1854,10 @@
 	/**
 	 * Get Hostname
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_hostname()
+	protected function _get_hostname()
 	{
 		return (isset($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : 'localhost.localdomain';
 	}
@@ -1867,10 +1867,10 @@
 	/**
 	 * Get IP
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @return	string
 	 */
-	private function _get_ip()
+	protected function _get_ip()
 	{
 		if ($this->_IP !== FALSE)
 		{
@@ -1933,11 +1933,11 @@
 	/**
 	 * Set Message
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @return	string
 	 */
-	private function _set_error_message($msg, $val = '')
+	protected function _set_error_message($msg, $val = '')
 	{
 		$CI =& get_instance();
 		$CI->lang->load('email');
@@ -1957,11 +1957,11 @@
 	/**
 	 * Mime Types
 	 *
-	 * @access	private
+	 * @access	protected
 	 * @param	string
 	 * @return	string
 	 */
-	private function _mime_types($ext = "")
+	protected function _mime_types($ext = "")
 	{
 		$mimes = array(	'hqx'	=>	'application/mac-binhex40',
 						'cpt'	=>	'application/mac-compactpro',
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index 6f79a55..a34809e 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -26,16 +26,15 @@
  */
 class CI_Form_validation {
 
-	var $CI;
-	var $_field_data			= array();
-	var $_config_rules			= array();
-	var $_error_array			= array();
-	var $_error_messages		= array();
-	var $_error_prefix			= '<p>';
-	var $_error_suffix			= '</p>';
-	var $error_string			= '';
-	var $_safe_form_data		= FALSE;
-
+	protected $CI;
+	protected $_field_data			= array();
+	protected $_config_rules		= array();
+	protected $_error_array			= array();
+	protected $_error_messages		= array();
+	protected $_error_prefix		= '<p>';
+	protected $_error_suffix		= '</p>';
+	protected $error_string			= '';
+	protected $_safe_form_data		= FALSE;
 
 	/**
 	 * Constructor
@@ -72,7 +71,7 @@
 	 * @param	string
 	 * @return	void
 	 */
-	function set_rules($field, $label = '', $rules = '')
+	public function set_rules($field, $label = '', $rules = '')
 	{
 		// No reason to set rules if we have no POST data
 		if (count($_POST) == 0)
@@ -163,7 +162,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function set_message($lang, $val = '')
+	public function set_message($lang, $val = '')
 	{
 		if ( ! is_array($lang))
 		{
@@ -187,7 +186,7 @@
 	 * @param	string
 	 * @return	void
 	 */
-	function set_error_delimiters($prefix = '<p>', $suffix = '</p>')
+	public function set_error_delimiters($prefix = '<p>', $suffix = '</p>')
 	{
 		$this->_error_prefix = $prefix;
 		$this->_error_suffix = $suffix;
@@ -206,7 +205,7 @@
 	 * @param	string	the field name
 	 * @return	void
 	 */
-	function error($field = '', $prefix = '', $suffix = '')
+	public function error($field = '', $prefix = '', $suffix = '')
 	{
 		if ( ! isset($this->_field_data[$field]['error']) OR $this->_field_data[$field]['error'] == '')
 		{
@@ -238,7 +237,7 @@
 	 * @param	string
 	 * @return	str
 	 */
-	function error_string($prefix = '', $suffix = '')
+	public function error_string($prefix = '', $suffix = '')
 	{
 		// No errrors, validation passes!
 		if (count($this->_error_array) === 0)
@@ -279,7 +278,7 @@
 	 * @access	public
 	 * @return	bool
 	 */
-	function run($group = '')
+	public function run($group = '')
 	{
 		// Do we even have any data to process?  Mm?
 		if (count($_POST) == 0)
@@ -374,7 +373,7 @@
 	 * @param	integer
 	 * @return	mixed
 	 */
-	function _reduce_array($array, $keys, $i = 0)
+	protected function _reduce_array($array, $keys, $i = 0)
 	{
 		if (is_array($array))
 		{
@@ -406,7 +405,7 @@
 	 * @access	private
 	 * @return	null
 	 */
-	function _reset_post_array()
+	protected function _reset_post_array()
 	{
 		foreach ($this->_field_data as $field => $row)
 		{
@@ -468,7 +467,7 @@
 	 * @param	integer
 	 * @return	mixed
 	 */
-	function _execute($row, $rules, $postdata = NULL, $cycles = 0)
+	protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
 	{
 		// If the $_POST data is an array we will run a recursive call
 		if (is_array($postdata))
@@ -489,7 +488,7 @@
 		if ( ! in_array('required', $rules) AND is_null($postdata))
 		{
 			// Before we bail out, does the rule contain a callback?
-			if (preg_match("/(callback_\w+)/", implode(' ', $rules), $match))
+			if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match))
 			{
 				$callback = TRUE;
 				$rules = (array('1' => $match[1]));
@@ -695,7 +694,7 @@
 	 * @param	string	the field name
 	 * @return	string
 	 */
-	function _translate_fieldname($fieldname)
+	protected function _translate_fieldname($fieldname)
 	{
 		// Do we need to translate the field name?
 		// We look for the prefix lang: to determine this
@@ -727,7 +726,7 @@
 	 * @param	string
 	 * @return	void
 	 */
-	function set_value($field = '', $default = '')
+	public function set_value($field = '', $default = '')
 	{
 		if ( ! isset($this->_field_data[$field]))
 		{
@@ -757,7 +756,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function set_select($field = '', $value = '', $default = FALSE)
+	public function set_select($field = '', $value = '', $default = FALSE)
 	{
 		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata']))
 		{
@@ -801,7 +800,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function set_radio($field = '', $value = '', $default = FALSE)
+	public function set_radio($field = '', $value = '', $default = FALSE)
 	{
 		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata']))
 		{
@@ -845,7 +844,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function set_checkbox($field = '', $value = '', $default = FALSE)
+	public function set_checkbox($field = '', $value = '', $default = FALSE)
 	{
 		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata']))
 		{
@@ -885,7 +884,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function required($str)
+	public function required($str)
 	{
 		if ( ! is_array($str))
 		{
@@ -907,7 +906,7 @@
 	 * @param	regex
 	 * @return	bool
 	 */
-	function regex_match($str, $regex)
+	public function regex_match($str, $regex)
 	{
 		if ( ! preg_match($regex, $str))
 		{
@@ -927,7 +926,7 @@
 	 * @param	field
 	 * @return	bool
 	 */
-	function matches($str, $field)
+	public function matches($str, $field)
 	{
 		if ( ! isset($_POST[$field]))
 		{
@@ -938,6 +937,24 @@
 
 		return ($str !== $field) ? FALSE : TRUE;
 	}
+	
+	// --------------------------------------------------------------------
+
+	/**
+	 * Match one field to another
+	 *
+	 * @access	public
+	 * @param	string
+	 * @param	field
+	 * @return	bool
+	 */
+	public function is_unique($str, $field)
+	{
+		list($table, $field)=explode('.', $field);
+		$query = $this->CI->db->limit(1)->get_where($table, array($field => $str));
+		
+		return $query->num_rows() === 0;
+    }
 
 	// --------------------------------------------------------------------
 
@@ -949,7 +966,7 @@
 	 * @param	value
 	 * @return	bool
 	 */
-	function min_length($str, $val)
+	public function min_length($str, $val)
 	{
 		if (preg_match("/[^0-9]/", $val))
 		{
@@ -974,7 +991,7 @@
 	 * @param	value
 	 * @return	bool
 	 */
-	function max_length($str, $val)
+	public function max_length($str, $val)
 	{
 		if (preg_match("/[^0-9]/", $val))
 		{
@@ -999,7 +1016,7 @@
 	 * @param	value
 	 * @return	bool
 	 */
-	function exact_length($str, $val)
+	public function exact_length($str, $val)
 	{
 		if (preg_match("/[^0-9]/", $val))
 		{
@@ -1023,7 +1040,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function valid_email($str)
+	public function valid_email($str)
 	{
 		return ( ! preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $str)) ? FALSE : TRUE;
 	}
@@ -1037,7 +1054,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function valid_emails($str)
+	public function valid_emails($str)
 	{
 		if (strpos($str, ',') === FALSE)
 		{
@@ -1064,7 +1081,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function valid_ip($ip)
+	public function valid_ip($ip)
 	{
 		return $this->CI->input->valid_ip($ip);
 	}
@@ -1078,7 +1095,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function alpha($str)
+	public function alpha($str)
 	{
 		return ( ! preg_match("/^([a-z])+$/i", $str)) ? FALSE : TRUE;
 	}
@@ -1092,7 +1109,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function alpha_numeric($str)
+	public function alpha_numeric($str)
 	{
 		return ( ! preg_match("/^([a-z0-9])+$/i", $str)) ? FALSE : TRUE;
 	}
@@ -1106,7 +1123,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function alpha_dash($str)
+	public function alpha_dash($str)
 	{
 		return ( ! preg_match("/^([-a-z0-9_-])+$/i", $str)) ? FALSE : TRUE;
 	}
@@ -1120,7 +1137,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function numeric($str)
+	public function numeric($str)
 	{
 		return (bool)preg_match( '/^[\-+]?[0-9]*\.?[0-9]+$/', $str);
 
@@ -1135,7 +1152,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function is_numeric($str)
+	public function is_numeric($str)
 	{
 		return ( ! is_numeric($str)) ? FALSE : TRUE;
 	}
@@ -1149,7 +1166,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function integer($str)
+	public function integer($str)
 	{
 		return (bool) preg_match('/^[\-+]?[0-9]+$/', $str);
 	}
@@ -1163,7 +1180,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function decimal($str)
+	public function decimal($str)
 	{
 		return (bool) preg_match('/^[\-+]?[0-9]+\.[0-9]+$/', $str);
 	}
@@ -1177,7 +1194,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function greater_than($str, $min)
+	public function greater_than($str, $min)
 	{
 		if ( ! is_numeric($str))
 		{
@@ -1195,7 +1212,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function less_than($str, $max)
+	public function less_than($str, $max)
 	{
 		if ( ! is_numeric($str))
 		{
@@ -1213,7 +1230,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function is_natural($str)
+	public function is_natural($str)
 	{
 		return (bool) preg_match( '/^[0-9]+$/', $str);
 	}
@@ -1227,7 +1244,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function is_natural_no_zero($str)
+	public function is_natural_no_zero($str)
 	{
 		if ( ! preg_match( '/^[0-9]+$/', $str))
 		{
@@ -1254,7 +1271,7 @@
 	 * @param	string
 	 * @return	bool
 	 */
-	function valid_base64($str)
+	public function valid_base64($str)
 	{
 		return (bool) ! preg_match('/[^a-zA-Z0-9\/\+=]/', $str);
 	}
@@ -1271,7 +1288,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function prep_for_form($data = '')
+	public function prep_for_form($data = '')
 	{
 		if (is_array($data))
 		{
@@ -1300,7 +1317,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function prep_url($str = '')
+	public function prep_url($str = '')
 	{
 		if ($str == 'http://' OR $str == '')
 		{
@@ -1324,7 +1341,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function strip_image_tags($str)
+	public function strip_image_tags($str)
 	{
 		return $this->CI->input->strip_image_tags($str);
 	}
@@ -1338,7 +1355,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function xss_clean($str)
+	public function xss_clean($str)
 	{
 		return $this->CI->security->xss_clean($str);
 	}
@@ -1352,7 +1369,7 @@
 	 * @param	string
 	 * @return	string
 	 */
-	function encode_php_tags($str)
+	public function encode_php_tags($str)
 	{
 		return str_replace(array('<?php', '<?PHP', '<?', '?>'),  array('&lt;?php', '&lt;?PHP', '&lt;?', '?&gt;'), $str);
 	}
diff --git a/system/libraries/Migration.php b/system/libraries/Migration.php
new file mode 100644
index 0000000..3943ec1
--- /dev/null
+++ b/system/libraries/Migration.php
@@ -0,0 +1,338 @@
+<?php defined('BASEPATH') OR exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * @package		CodeIgniter
+ * @author		EllisLab Dev Team
+ * @copyright	Copyright (c) 2006 - 2011, EllisLab, Inc.
+ * @license		http://codeigniter.com/user_guide/license.html
+ * @link		http://codeigniter.com
+ * @since		Version 1.0
+ * @filesource
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * Migration Class
+ *
+ * All migrations should implement this, forces up() and down() and gives
+ * access to the CI super-global.
+ *
+ * @package		CodeIgniter
+ * @subpackage	Libraries
+ * @category	Libraries
+ * @author		Reactor Engineers
+ * @link
+ */
+class CI_Migration {
+
+	protected $_migration_enabled = FALSE;
+	protected $_migration_path = NULL;
+	protected $_migration_version = 0;
+
+	protected $_error_string = '';
+
+	public function __construct($config = array())
+	{
+		# Only run this constructor on main library load
+		if (get_parent_class($this) !== FALSE)
+		{
+			return;
+		}
+
+		foreach ($config as $key => $val)
+		{
+			$this->{'_' . $key} = $val;
+		}
+
+		log_message('debug', 'Migrations class initialized');
+
+		// Are they trying to use migrations while it is disabled?
+		if ($this->_migration_enabled !== TRUE)
+		{
+			show_error('Migrations has been loaded but is disabled or set up incorrectly.');
+		}
+
+		// If not set, set it
+		$this->_migration_path == '' OR $this->_migration_path = APPPATH . 'migrations/';
+
+		// Add trailing slash if not set
+		$this->_migration_path = rtrim($this->_migration_path, '/').'/';
+
+		// Load migration language
+		$this->lang->load('migration');
+
+		// They'll probably be using dbforge
+		$this->load->dbforge();
+
+		// If the migrations table is missing, make it
+		if ( ! $this->db->table_exists('migrations'))
+		{
+			$this->dbforge->add_field(array(
+				'version' => array('type' => 'INT', 'constraint' => 3),
+			));
+
+			$this->dbforge->create_table('migrations', TRUE);
+
+			$this->db->insert('migrations', array('version' => 0));
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Migrate to a schema version
+	 *
+	 * Calls each migration step required to get to the schema version of
+	 * choice
+	 *
+	 * @access	public
+	 * @param $version integer	Target schema version
+	 * @return	mixed	TRUE if already latest, FALSE if failed, int if upgraded
+	 */
+	public function version($target_version)
+	{
+		$start = $current_version = $this->_get_version();
+		$stop = $target_version;
+
+		if ($target_version > $current_version)
+		{
+			// Moving Up
+			++$start;
+			++$stop;
+			$step = 1;
+		}
+
+		else
+		{
+			// Moving Down
+			$step = -1;
+		}
+		
+		$method = $step === 1 ? 'up' : 'down';
+		$migrations = array();
+
+		// We now prepare to actually DO the migrations
+		// But first let's make sure that everything is the way it should be
+		for ($i = $start; $i != $stop; $i += $step)
+		{
+			$f = glob(sprintf($this->_migration_path . '%03d_*.php', $i));
+
+			// Only one migration per step is permitted
+			if (count($f) > 1)
+			{
+				$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
+				return FALSE;
+			}
+
+			// Migration step not found
+			if (count($f) == 0)
+			{
+				// If trying to migrate up to a version greater than the last
+				// existing one, migrate to the last one.
+				if ($step == 1)
+				{
+					break;
+				}
+
+				// If trying to migrate down but we're missing a step,
+				// something must definitely be wrong.
+				$this->_error_string = sprintf($this->lang->line('migration_not_found'), $i);
+				return FALSE;
+			}
+
+			$file = basename($f[0]);
+			$name = basename($f[0], '.php');
+
+			// Filename validations
+			if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
+			{
+				$match[1] = strtolower($match[1]);
+
+				// Cannot repeat a migration at different steps
+				if (in_array($match[1], $migrations))
+				{
+					$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
+					return FALSE;
+				}
+
+				include $f[0];
+				$class = 'Migration_' . ucfirst($match[1]);
+
+				if ( ! class_exists($class))
+				{
+					$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
+					return FALSE;
+				}
+
+				if ( ! is_callable(array($class, $method)))
+				{
+					$this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
+					return FALSE;
+				}
+
+				$migrations[] = $match[1];
+			}
+			else
+			{
+				$this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
+				return FALSE;
+			}
+		}
+
+		log_message('debug', 'Current migration: ' . $current_version);
+
+		$version = $i + ($step == 1 ? -1 : 0);
+
+		// If there is nothing to do so quit
+		if ($migrations === array())
+		{
+			return TRUE;
+		}
+
+		log_message('debug', 'Migrating from ' . $method . ' to version ' . $version);
+
+		// Loop through the migrations
+		foreach ($migrations AS $migration)
+		{
+			// Run the migration class
+			$class = 'Migration_' . ucfirst(strtolower($migration));
+			call_user_func(array(new $class, $method));
+
+			$current_version += $step;
+			$this->_update_version($current_version);
+		}
+
+		log_message('debug', 'Finished migrating to '.$current_version);
+
+		return $current_version;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set's the schema to the latest migration
+	 *
+	 * @access	public
+	 * @return	mixed	true if already latest, false if failed, int if upgraded
+	 */
+	public function latest()
+	{
+		if ( ! $migrations = $this->find_migrations())
+		{
+			$this->_error_string = $this->line->lang('migration_none_found');
+			return false;
+		}
+
+		$last_migration = basename(end($migrations));
+		
+		// Calculate the last migration step from existing migration
+		// filenames and procceed to the standard version migration
+		return $this->version((int) substr($last_migration, 0, 3));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set's the schema to the migration version set in config
+	 *
+	 * @access	public
+	 * @return	mixed	true if already current, false if failed, int if upgraded
+	 */
+	public function current()
+	{
+		return $this->version($this->_migration_version);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Error string
+	 *
+	 * @access	public
+	 * @return	string	Error message returned as a string
+	 */
+	public function error_string()
+	{
+		return $this->_error_string;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Set's the schema to the latest migration
+	 *
+	 * @access	protected
+	 * @return	mixed	true if already latest, false if failed, int if upgraded
+	 */
+	protected function find_migrations()
+	{
+		// Load all *_*.php files in the migrations path
+		$files = glob($this->_migration_path . '*_*.php');
+		$file_count = count($files);
+		
+		for ($i = 0; $i < $file_count; $i++)
+		{
+			// Mark wrongly formatted files as false for later filtering
+			$name = basename($files[$i], '.php');
+			if ( ! preg_match('/^\d{3}_(\w+)$/', $name))
+			{
+				$files[$i] = FALSE;
+			}
+		}
+		
+		sort($files);
+
+		return $files;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Retrieves current schema version
+	 *
+	 * @access	protected
+	 * @return	integer	Current Migration
+	 */
+	protected function _get_version()
+	{
+		$row = $this->db->get('migrations')->row();
+		return $row ? $row->version : 0;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Stores the current schema version
+	 *
+	 * @access	protected
+	 * @param $migrations integer	Migration reached
+	 * @return	void					Outputs a report of the migration
+	 */
+	protected function _update_version($migrations)
+	{
+		return $this->db->update('migrations', array(
+			'version' => $migrations
+		));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Enable the use of CI super-global
+	 *
+	 * @access	public
+	 * @param $var
+	 * @return	mixed
+	 */
+	public function __get($var)
+	{
+		return get_instance()->$var;
+	}
+}
+
+/* End of file Migration.php */
+/* Location: ./system/libraries/Migration.php */
\ No newline at end of file
diff --git a/system/libraries/User_agent.php b/system/libraries/User_agent.php
index 016102a..0b77a7d 100644
--- a/system/libraries/User_agent.php
+++ b/system/libraries/User_agent.php
@@ -142,7 +142,7 @@
 	{
 		$this->_set_platform();
 
-		foreach (array('_set_browser', '_set_robot', '_set_mobile') as $function)
+		foreach (array('_set_robot', '_set_browser', '_set_mobile') as $function)
 		{
 			if ($this->$function() === TRUE)
 			{
diff --git a/user_guide/changelog.html b/user_guide/changelog.html
index 5d5767b..c4894d8 100644
--- a/user_guide/changelog.html
+++ b/user_guide/changelog.html
@@ -59,6 +59,41 @@
 
 <p>The <img src="images/reactor-bullet.png" width="16" height="16" alt="Reactor Marker" /> indicates items that were contributed to CodeIgniter via CodeIgniter Reactor.</p>
 
+<h2>Version 2.1.0</h2>
+<p>Release Date: Not Released</p>
+
+<ul>
+	<li>General Changes
+		<ul>
+			<li class="reactor">Callback validation rules can now accept parameters like any other validation rule.</li>
+		</ul>
+	</li>
+	<li>Helpers
+		<ul>
+		</ul>
+	</li>
+	<li>Database
+		<ul>
+			<li class="reactor">Added a <a href="http://www.cubrid.org/" target="_blank">CUBRID</a> driver to the <a href="libraries/database.html">Database Driver</a>. Thanks to the CUBRID team for supplying this patch.</li>
+			<li class="reactor">Typecast limit and offset in the <a href="database/queries.html">Database Driver</a> to integers to avoid possible injection.</li>
+		</ul>
+	</li>
+	<li>Libraries
+		<ul>
+			<li class="reactor">Changed <kbd>$this->cart->insert()</kbd> in the <a href="libraries/cart.html">Cart Library</a> to return the Row ID if a single item was inserted successfully.</li>
+			<li class="reactor">Added support to set an optional parameter in your callback rules of validation using the <a href="libraries/form_validation.html">Form Validation library</a>.</li>
+			<li class="reactor">Added a <a href="libraries/migration.html">Migration library</a> to assist with applying incremental updates to your database schema.</li>
+		</ul>
+	</li>
+</ul>
+
+<h3>Bug fixes for 2.1.0</h3>
+<ul>
+	<li class="reactor">Fixed #378 Robots identified as regular browsers by the User Agent class.</li>
+	<li class="reactor">If a config class was loaded first then a library with the same name is loaded, the config would be ignored.</li>
+	<li class="reactor">Fixed a bug (Reactor #19) where 1) the 404_override route was being ignored in some cases, and 2) auto-loaded libraries were not available to the 404_override controller when a controller existed but the requested method did not.</li>
+</ul>
+
 <h2>Version 2.0.3</h2>
 <p>Release Date: Not Released</p>
 
@@ -67,6 +102,7 @@
 		<ul>
 			<li>An improvement was made to the MySQL and MySQLi drivers to prevent exposing a potential vector for SQL injection on sites using multi-byte character sets in the database client connection.  <p>An incompatibility in PHP versions &lt; 5.2.3 and MySQL &lt; 5.0.7 with <em>mysql_set_charset()</em> creates a situation where using multi-byte  character sets on these environments may potentially expose a SQL injection attack vector.  Latin-1, UTF-8, and other "low ASCII" character sets are unaffected on all environments.</p>  <p class="critical">If you are running or considering running a multi-byte character set for your database connection, please pay close attention to the server environment you are deploying on to ensure you are not vulnerable.</p></li>
 		</ul>
+	</li>
 	<li>General Changes
 		<ul>
 			<li>Fixed a bug where there was a misspelling within a code comment in the index.php file.</li>
@@ -75,12 +111,13 @@
 			<li>Visual updates to the welcome_message view file and default error templates. Thanks to <a href="https://bitbucket.org/danijelb">danijelb</a> for the pull request.</li>
 			<li class="reactor">Added <samp>insert_batch()</samp> function to the PostgreSQL database driver.  Thanks to epallerols for the patch.</li>
 			<li class="reactor">Added "application/x-csv" to mimes.php.</li>
-			<li>Fixed a bug where <a href="libraries/email.html">Email library</a> attachments with a "." in the name would using invalid MIME-types.</li>
 		</ul>
 	</li>
 	<li>Helpers
 		<ul>
 			<li>Added an optional third parameter to <samp>heading()</samp> which allows adding html attributes to the rendered heading tag.</li>
+			<li class="reactor"><kbd>form_open()</kbd> now only adds a hidden (Cross-site Reference Forgery) protection field when the form's action is internal and is set to the post method. (Reactor #165)</li>
+			<li class="reactor">Re-worked <samp>plural()</samp> and <samp>singular()</samp> functions in the <a href="helpers/inflector_helper.html">Inflector helper</a> to support considerably more words.</li>
 		</ul>
 	</li>
 	<li>Libraries
@@ -90,7 +127,7 @@
 			<li class="reactor">Added <kbd>$this->db->set_dbprefix()</kbd> to the <a href="database/queries.html">Database Driver</a>.</li>
 			<li class="reactor">Changed <kbd>$this->cart->insert()</kbd> in the <a href="libraries/cart.html">Cart Library</a> to return the Row ID if a single item was inserted successfully.</li>
 			<li class="reactor">Added <kbd>$this->load->get_var()</kbd> to the <a href="libraries/loader.html">Loader library</a> to retrieve global vars set with <kbd>$this->load->view()</kbd> and <kbd>$this->load->vars()</kbd>.</li>
-			<li>Changed <kbd>$this->db->having()</kbd> to insert quotes using escape() rather than escape_str().</li>
+			<li class="reactor">Changed <kbd>$this->db->having()</kbd> to insert quotes using escape() rather than escape_str().</li>
 		</ul>
 	</li>
 </ul>
@@ -105,15 +142,10 @@
 	<li>Fixed a bug (Core #340) where when passing in the second parameter to $this->db->select(), column names in subsequent queries would not be properly escaped.</li>
 	<li class="reactor">Fixed issue #199 - Attributes passed as string does not include a space between it and the opening tag.</li>
 	<li class="reactor">Fixed a bug where the method <kbd>$this->cart->total_items()</kbd> from <a href="libraries/cart.html">Cart Library</a> now returns the sum of the quantity of all items in the cart instead of your total count.</li>
-<<<<<<< HEAD
 	<li class="reactor">Fixed a bug where not setting 'null' when adding fields in db_forge for mysql and mysqli drivers would default to NULL instead of NOT NULL as the docs suggest.</li>
 	<li class="reactor">Fixed a bug where using <kbd>$this->db->select_max()</kdb>, <kbd>$this->db->select_min()</kdb>, etc could throw notices. Thanks to w43l for the patch.</li>
 	<li class="reactor">Replace checks for STDIN with php_sapi_name() == 'cli' which on the whole is more reliable. This should get parameters in crontab working.</li>
-=======
-	<li>Fixed a bug where not setting 'null' when adding fields in db_forge for mysql and mysqli drivers would default to NULL instead of NOT NULL as the docs suggest.</li>
-	<li>Fixed a bug where using <kbd>$this->db->select_max()</kdb>, <kbd>$this->db->select_min()</kdb>, etc could throw notices. Thanks to w43l for the patch.</li>
 	<li>Fixed a bug where <a href="libraries/email.html">Email library</a> attachments with a "." in the name would using invalid MIME-types.</li>
->>>>>>> 0aaf42b... Fixed a bug where Email library attachments with a . in the name would using invalid MIME-types.
 </ul>
 
 <h2>Version 2.0.2</h2>
diff --git a/user_guide/database/configuration.html b/user_guide/database/configuration.html
index 51d11c9..4ea2e6b 100644
--- a/user_guide/database/configuration.html
+++ b/user_guide/database/configuration.html
@@ -132,8 +132,8 @@
 <li><strong>cache_on</strong> - TRUE/FALSE (boolean) - Whether database query caching is enabled, see also <a href="caching.html">Database Caching Class</a>.</li>
 <li><strong>cachedir</strong> - The absolute server path to your database query cache directory.</li>
 <li><strong>char_set</strong> - The character set used in communicating with the database.</li>
-<li><strong>dbcollat</strong> - The character collation used in communicating with the database. <p class="important"><strong>Note:</strong> For MySQL and MySQLi databases, this setting is only used as a backup if your server is running PHP &lt; 5.2.3 or MySQL &lt; 5.0.7.  There is an incompatibility in PHP with mysql_real_escape_string() which can make your site vulnerable to SQL injection if you are using a multi-byte character set and are running versions lower than these.  Sites using Latin-1 or UTF-8 database character set and collation are unaffected.</p></li>
-<li><strong>swap_pre</strong> - A default table prefix that should be swapped with <var>dbprefix</var>.  This is useful for distributed applications where you might run manually written queries, and need the prefix to still be customizable by the end user.</li>
+<li><strong>dbcollat</strong> - The character collation used in communicating with the database. <p class="important"><strong>Note:</strong> For MySQL and MySQLi databases, this setting is only used as a backup if your server is running PHP &lt; 5.2.3 or MySQL &lt; 5.0.7 (and in table creation queries made with DB Forge). There is an incompatibility in PHP with mysql_real_escape_string() which can make your site vulnerable to SQL injection if you are using a multi-byte character set and are running versions lower than these. Sites using Latin-1 or UTF-8 database character set and collation are unaffected.</p></li>
+<li><strong>swap_pre</strong> - A default table prefix that should be swapped with <var>dbprefix</var>. This is useful for distributed applications where you might run manually written queries, and need the prefix to still be customizable by the end user.</li>
 <li><strong>autoinit</strong> - Whether or not to automatically connect to the database when the library loads. If set to false, the connection will take place prior to executing the first query.</li>
 <li><strong>stricton</strong> - TRUE/FALSE (boolean) - Whether to force "Strict Mode" connections, good for ensuring strict SQL while developing an application.</li>
 <li><strong>port</strong> - The database port number.  To use this value you have to add a line to the database config array.<code>$db['default']['port'] =  5432;</code>
diff --git a/user_guide/helpers/url_helper.html b/user_guide/helpers/url_helper.html
index de28a6f..ac9d0a6 100644
--- a/user_guide/helpers/url_helper.html
+++ b/user_guide/helpers/url_helper.html
@@ -27,7 +27,7 @@
 <div id="masthead">
 <table cellpadding="0" cellspacing="0" border="0" style="width:100%">
 <tr>
-<td><h1>CodeIgniter User Guide Version 2.0.0</h1></td>
+<td><h1>CodeIgniter User Guide Version 2.0.2</h1></td>
 <td id="breadcrumb_right"><a href="../toc.html">Table of Contents Page</a></td>
 </tr>
 </table>
diff --git a/user_guide/images/appflowchart.gif b/user_guide/images/appflowchart.gif
index 422332c..4328e48 100644
--- a/user_guide/images/appflowchart.gif
+++ b/user_guide/images/appflowchart.gif
Binary files differ
diff --git a/user_guide/libraries/cart.html b/user_guide/libraries/cart.html
index f084d5d..2e2beed 100644
--- a/user_guide/libraries/cart.html
+++ b/user_guide/libraries/cart.html
@@ -61,7 +61,7 @@
 <p>The Cart Class permits items to be added to a session that stays active while a user is browsing your site.
 These items can be retrieved and displayed in a standard "shopping cart" format, allowing the user to update the quantity or remove items from the cart.</p>
 
-<p>Please note that the Cart Class ONLY provides the core "cart" functionality.  It does not provide shipping, credit card authorization, or other processing components.</p>
+<p>Please note that the Cart Class ONLY provides the core "cart" functionality. It does not provide shipping, credit card authorization, or other processing components.</p>
 
 
 <h2>Initializing the Shopping Cart Class</h2>
@@ -106,20 +106,20 @@
 <li><strong>qty</strong> - The quantity being purchased.
 <li><strong>price</strong> - The price of the item.
 <li><strong>name</strong> - The name of the item.
-<li><strong>options</strong> - Any additional attributes that are needed to identify the product.  These must be passed via an array.
+<li><strong>options</strong> - Any additional attributes that are needed to identify the product. These must be passed via an array.
 </ul>
 
 <p>In addition to the five indexes above, there are two reserved words: <dfn>rowid</dfn> and <dfn>subtotal</dfn>.  These are used internally by the Cart class, so
 please do NOT use those words as index names when inserting data into the cart.</p>
 
-<p>Your array may contain additional data.  Anything you include in your array will be stored in the session.  However, it is best to standardize your data among
-all your products in order to make displaying the information in a table easier.</p>
+<p>Your array may contain additional data. Anything you include in your array will be stored in the session. However, it is best to standardize your data among all your products in order to make displaying the information in a table easier.</p>
+
+<p>The insert() method will return the $rowid if you successfully insert a single item.</p>
 
 
 <h2>Adding Multiple Items to The Cart</h2>
 
-<p>By using a multi-dimensional array, as shown below, it is possible to add multiple products to the cart in one action.  This is useful in cases where you wish to allow
-people to select from among several items on the same page.</p>
+<p>By using a multi-dimensional array, as shown below, it is possible to add multiple products to the cart in one action. This is useful in cases where you wish to allow people to select from among several items on the same page.</p>
 
 
 <code>
@@ -268,8 +268,8 @@
 <p><strong>What is a Row ID?</strong>&nbsp; The <kbd>row ID</kbd> is a unique identifier that is generated by the cart code when an item is added to the cart.  The reason a
 unique ID is created is so that identical products with different options can be managed by the cart.</p>
 
-<p>For example, let's say someone buys two identical t-shirts (same product ID), but in different sizes.  The product ID (and other attributes) will be
-identical for both sizes because it's the same shirt. The only difference will be the size.  The cart must therefore have a means of identifying this
+<p>For example, let's say someone buys two identical t-shirts (same product ID), but in different sizes. The product ID (and other attributes) will be
+identical for both sizes because it's the same shirt. The only difference will be the size. The cart must therefore have a means of identifying this
 difference so that the two sizes of shirts can be managed independently. It does so by creating a unique "row ID" based on the product ID and any options associated with it.</p>
 
 <p>In nearly all cases, updating the cart will be something the user does via the "view cart" page, so as a developer, it is unlikely that you will ever have to concern yourself
@@ -311,7 +311,7 @@
 
 <h2>$this->cart->has_options(rowid);</h2>
 
-<p>Returns TRUE (boolean) if a particular row in the cart contains options.  This function is designed to be used in a loop with <dfn>$this->cart->contents()</dfn>, since you must pass the <kbd>rowid</kbd> to this function, as shown in the <dfn>Displaying the Cart</dfn> example above.</p>
+<p>Returns TRUE (boolean) if a particular row in the cart contains options. This function is designed to be used in a loop with <dfn>$this->cart->contents()</dfn>, since you must pass the <kbd>rowid</kbd> to this function, as shown in the <dfn>Displaying the Cart</dfn> example above.</p>
 
 
 <h2>$this->cart->product_options(rowid);</h2>
@@ -322,7 +322,7 @@
 
 <h2>$this->cart->destroy();</h2>
 
-<p>Permits you to destroy the cart.  This function will likely be called when you are finished processing the customer's order.</p>
+<p>Permits you to destroy the cart. This function will likely be called when you are finished processing the customer's order.</p>
 
 
 
diff --git a/user_guide/libraries/form_validation.html b/user_guide/libraries/form_validation.html
index 8fdcd14..da2f5e5 100644
--- a/user_guide/libraries/form_validation.html
+++ b/user_guide/libraries/form_validation.html
@@ -390,10 +390,10 @@
 <p>CodeIgniter lets you pipe multiple rules together.  Let's try it. Change your rules in the third parameter of rule setting function, like this:</p>
 
 <code>
-$this->form_validation->set_rules('username', 'Username', 'required|min_length[5]|max_length[12]');<br />
+$this->form_validation->set_rules('username', 'Username', 'required|min_length[5]|max_length[12]|is_unique[users.username]');<br />
 $this->form_validation->set_rules('password', 'Password', 'required|matches[passconf]');<br />
 $this->form_validation->set_rules('passconf', 'Password Confirmation', 'required');<br />
-$this->form_validation->set_rules('email', 'Email', 'required|valid_email');<br />
+$this->form_validation->set_rules('email', 'Email', 'required|valid_email|is_unique[users.email]');<br />
 </code>
 
 <p>The above code sets the following rules:</p>
@@ -508,15 +508,13 @@
 
 <code>$this->form_validation->set_rules('username', 'Username', '<kbd>callback_username_check</kbd>');</code>
 
-
 <p>Then add a new function called <dfn>username_check</dfn> to your controller.  Here's how your controller should now look:</p>
 
-
-<textarea class="textarea" style="width:100%" cols="50" rows="44">&lt;?php
+<textarea class="textarea" style="width:100%" cols="50" rows="40">&lt;?php
 
 class Form extends CI_Controller {
 
-	function index()
+	public function index()
 	{
 		$this->load->helper(array('form', 'url'));
 
@@ -525,7 +523,7 @@
 		$this->form_validation->set_rules('username', 'Username', 'callback_username_check');
 		$this->form_validation->set_rules('password', 'Password', 'required');
 		$this->form_validation->set_rules('passconf', 'Password Confirmation', 'required');
-		$this->form_validation->set_rules('email', 'Email', 'required');
+		$this->form_validation->set_rules('email', 'Email', 'required|is_unique[users.email]');
 
 		if ($this->form_validation->run() == FALSE)
 		{
@@ -537,7 +535,7 @@
 		}
 	}
 
-	function username_check($str)
+	public function username_check($str)
 	{
 		if ($str == 'test')
 		{
@@ -556,14 +554,13 @@
 <p><dfn>Reload your form and submit it with the word "test" as the username.  You can see that the form field data was passed to your
 callback function for you to process.</dfn></p>
 
-<p><strong>To invoke a callback just put the function name in a rule, with "callback_" as the rule prefix.</strong></p>
+<p>To invoke a callback just put the function name in a rule, with "callback_" as the rule <strong>prefix</strong>. If you need
+to receive an extra parameter in your callback function, just add it normally after the function name between square brackets,
+as in: "callback_foo<strong>[bar]</strong>", then it will be passed as the second argument of your callback function.</p>
 
-<p>You can also process the form data that is passed to your callback and return it.  If your callback returns anything other than a boolean TRUE/FALSE
+<p><strong>Note:</strong> You can also process the form data that is passed to your callback and return it.  If your callback returns anything other than a boolean TRUE/FALSE
 it is assumed that the data is your newly processed form data.</p>
 
-
-
-
 <a name="settingerrors"></a>
 <h2>Setting Error Messages</h2>
 
@@ -947,6 +944,13 @@
 	</tr>
 
 	<tr>
+		<td class="td"><strong>is_unique</strong></td>
+		<td class="td">Yes</td>
+		<td class="td">Returns FALSE if the form element is not unique to the table and field name in the parameter.</td>
+		<td class="td">is_unique[table.field]</td>
+	</tr>
+
+	<tr>
 		<td class="td"><strong>min_length</strong></td>
 		<td class="td">Yes</td>
 		<td class="td">Returns FALSE if the form element is shorter then the parameter value.</td>
diff --git a/user_guide/overview/appflow.html b/user_guide/overview/appflow.html
index bcbc43f..7c8d4ac 100644
--- a/user_guide/overview/appflow.html
+++ b/user_guide/overview/appflow.html
@@ -60,7 +60,7 @@
 
 <p>The following graphic illustrates how data flows throughout the system:</p>
 
-<div><img src="../images/appflowchart.gif" width="697" height="205" border="0" alt="CodeIgniter application flow" /></div>
+<div><img src="../images/appflowchart.gif" width="769" height="212" alt="CodeIgniter application flow"></div>
 
 
 <ol>