Merge upstream branch
diff --git a/.travis.yml b/.travis.yml
index 29111bc..84029b9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,12 +4,20 @@
   - 5.3
   - 5.4
 
+env:
+  - DB=mysql
+  - DB=pgsql
+  - DB=sqlite
+
 before_script:
   - pyrus channel-discover pear.php-tools.net
   - pyrus install http://pear.php-tools.net/get/vfsStream-0.11.2.tgz
   - phpenv rehash
+  - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS ci_test;' -U postgres; fi"
+  - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database ci_test;' -U postgres; fi"
+  - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi"
 
-script: phpunit --configuration tests/phpunit.xml 
+script: phpunit --configuration tests/travis/$DB.phpunit.xml
 
 branches:
   only:
diff --git a/system/database/DB.php b/system/database/DB.php
index 96e4955..0d81e40 100755
--- a/system/database/DB.php
+++ b/system/database/DB.php
@@ -92,16 +92,12 @@
 		if (isset($dsn['query']))
 		{
 			parse_str($dsn['query'], $extra);
+
 			foreach ($extra as $key => $val)
 			{
-				// booleans please
-				if (strtoupper($val) === 'TRUE')
+				if (is_string($val) && in_array(strtoupper($val), array('TRUE', 'FALSE', 'NULL')))
 				{
-					$val = TRUE;
-				}
-				elseif (strtoupper($val) === 'FALSE')
-				{
-					$val = FALSE;
+					$val = var_export($val);
 				}
 
 				$params[$key] = $val;
@@ -138,7 +134,12 @@
 		class CI_DB extends CI_DB_driver { }
 	}
 
-	require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php');
+	// Load the DB driver
+	$driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php';
+
+	if ( ! file_exists($driver_file)) show_error('Invalid DB driver');
+
+	require_once($driver_file);
 
 	// Instantiate the DB adapter
 	$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 352e3e8..dea7050 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -1349,7 +1349,9 @@
 	 *
 	 * @return	void
 	 */
-	abstract protected function _reset_select();
+	protected function _reset_select()
+	{
+	}
 
 }
 
diff --git a/system/database/DB_result.php b/system/database/DB_result.php
index 04f964f..bb09c01 100644
--- a/system/database/DB_result.php
+++ b/system/database/DB_result.php
@@ -57,7 +57,7 @@
 	 * Query result.  Acts as a wrapper function for the following functions.
 	 *
 	 * @param	string	can be "object" or "array"
-	 * @return	mixed	either a result object or array
+	 * @return	object
 	 */
 	public function result($type = 'object')
 	{
@@ -108,9 +108,9 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Query result.  "object" version.
+	 * Query result. "object" version.
 	 *
-	 * @return	object
+	 * @return	array
 	 */
 	public function result_object()
 	{
@@ -224,7 +224,7 @@
 			return;
 		}
 
-		if ($key != '' AND ! is_null($value))
+		if ($key != '' && ! is_null($value))
 		{
 			$this->row_data[$key] = $value;
 		}
@@ -245,7 +245,7 @@
 			return $result;
 		}
 
-		if ($n != $this->current_row AND isset($result[$n]))
+		if ($n != $this->current_row && isset($result[$n]))
 		{
 			$this->current_row = $n;
 		}
@@ -266,7 +266,7 @@
 			return $result;
 		}
 
-		if ($n != $this->current_row AND isset($result[$n]))
+		if ($n != $this->current_row && isset($result[$n]))
 		{
 			$this->current_row = $n;
 		}
@@ -289,7 +289,7 @@
 			return $result;
 		}
 
-		if ($n != $this->current_row AND isset($result[$n]))
+		if ($n != $this->current_row && isset($result[$n]))
 		{
 			$this->current_row = $n;
 		}
@@ -297,7 +297,6 @@
 		return $result[$this->current_row];
 	}
 
-
 	// --------------------------------------------------------------------
 
 	/**
@@ -374,9 +373,9 @@
 	/**
 	 * The following functions are normally overloaded by the identically named
 	 * methods in the platform-specific driver -- except when query caching
-	 * is used.  When caching is enabled we do not load the other driver.
+	 * is used. When caching is enabled we do not load the other driver.
 	 * These functions are primarily here to prevent undefined function errors
-	 * when a cached result object is in use.  They are not otherwise fully
+	 * when a cached result object is in use. They are not otherwise fully
 	 * operational due to the unavailability of the database resource IDs with
 	 * cached results.
 	 */
@@ -384,7 +383,7 @@
 	public function num_fields() { return 0; }
 	public function list_fields() { return array(); }
 	public function field_data() { return array(); }
-	public function free_result() { return TRUE; }
+	public function free_result() { $this->result_id = FALSE; }
 	protected function _data_seek() { return TRUE; }
 	protected function _fetch_assoc() { return array(); }
 	protected function _fetch_object() { return array(); }
diff --git a/system/database/drivers/postgre/postgre_result.php b/system/database/drivers/postgre/postgre_result.php
index 8b22564..394b8b6 100644
--- a/system/database/drivers/postgre/postgre_result.php
+++ b/system/database/drivers/postgre/postgre_result.php
@@ -70,7 +70,7 @@
 	public function list_fields()
 	{
 		$field_names = array();
-		for ($i = 0; $i < $this->num_fields(); $i++)
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 		{
 			$field_names[] = pg_field_name($this->result_id, $i);
 		}
@@ -90,16 +90,14 @@
 	public function field_data()
 	{
 		$retval = array();
-		for ($i = 0; $i < $this->num_fields(); $i++)
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 		{
-			$F				= new stdClass();
-			$F->name		= pg_field_name($this->result_id, $i);
-			$F->type		= pg_field_type($this->result_id, $i);
-			$F->max_length	= pg_field_size($this->result_id, $i);
-			$F->primary_key = 0;
-			$F->default		= '';
-
-			$retval[] = $F;
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= pg_field_name($this->result_id, $i);
+			$retval[$i]->type		= pg_field_type($this->result_id, $i);
+			$retval[$i]->max_length		= pg_field_size($this->result_id, $i);
+			$retval[$i]->primary_key	= 0;
+			$retval[$i]->default		= '';
 		}
 
 		return $retval;
diff --git a/system/database/drivers/sqlite/sqlite_result.php b/system/database/drivers/sqlite/sqlite_result.php
index b002aef..4af80ab 100644
--- a/system/database/drivers/sqlite/sqlite_result.php
+++ b/system/database/drivers/sqlite/sqlite_result.php
@@ -70,9 +70,9 @@
 	public function list_fields()
 	{
 		$field_names = array();
-		for ($i = 0; $i < $this->num_fields(); $i++)
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 		{
-			$field_names[] = sqlite_field_name($this->result_id, $i);
+			$field_names[$i] = sqlite_field_name($this->result_id, $i);
 		}
 
 		return $field_names;
@@ -90,16 +90,14 @@
 	public function field_data()
 	{
 		$retval = array();
-		for ($i = 0; $i < $this->num_fields(); $i++)
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
 		{
-			$F				= new stdClass();
-			$F->name		= sqlite_field_name($this->result_id, $i);
-			$F->type		= 'varchar';
-			$F->max_length	= 0;
-			$F->primary_key = 0;
-			$F->default		= '';
-
-			$retval[] = $F;
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= sqlite_field_name($this->result_id, $i);
+			$retval[$i]->type		= 'varchar';
+			$retval[$i]->max_length		= 0;
+			$retval[$i]->primary_key	= 0;
+			$retval[$i]->default		= '';
 		}
 
 		return $retval;
@@ -108,18 +106,6 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Free the result
-	 *
-	 * @return	void
-	 */
-	public function free_result()
-	{
-		// Not implemented in SQLite
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
 	 * Data Seek
 	 *
 	 * Moves the internal pointer to the desired offset. We call
@@ -162,17 +148,9 @@
 		{
 			return sqlite_fetch_object($this->result_id);
 		}
-		else
-		{
-			$arr = sqlite_fetch_array($this->result_id, SQLITE_ASSOC);
-			if (is_array($arr))
-			{
-				$obj = (object) $arr;
-				return $obj;
-			} else {
-				return NULL;
-			}
-		}
+
+		$arr = sqlite_fetch_array($this->result_id, SQLITE_ASSOC);
+		return is_array($arr) ? (object) $arr : FALSE;
 	}
 
 }
diff --git a/system/database/drivers/sqlite3/index.html b/system/database/drivers/sqlite3/index.html
new file mode 100644
index 0000000..c942a79
--- /dev/null
+++ b/system/database/drivers/sqlite3/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/sqlite3/sqlite3_driver.php b/system/database/drivers/sqlite3/sqlite3_driver.php
new file mode 100644
index 0000000..de37052
--- /dev/null
+++ b/system/database/drivers/sqlite3/sqlite3_driver.php
@@ -0,0 +1,521 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license	http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link	http://codeigniter.com
+ * @since	Version 1.0
+ * @filesource
+ */
+
+/**
+ * SQLite3 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	Andrey Andreev
+ * @link	http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlite3_driver extends CI_DB {
+
+	public $dbdriver = 'sqlite3';
+
+	// The character used for escaping
+	protected $_escape_char = '"';
+
+	// clause and character used for LIKE escape sequences
+	protected $_like_escape_str = ' ESCAPE \'%s\' ';
+	protected $_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.
+	 */
+	protected $_count_string = 'SELECT COUNT(*) AS ';
+	protected $_random_keyword = ' RANDOM()';
+
+	/**
+	 * Non-persistent database connection
+	 *
+	 * @return	object	type SQLite3
+	 */
+	public function db_connect()
+	{
+		try
+		{
+			return ( ! $this->password)
+				? new SQLite3($this->database)
+				: new SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password);
+		}
+		catch (Exception $e)
+		{
+			return FALSE;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Persistent database connection
+	 *
+	 * @return  object	type SQLite3
+	 */
+	public function db_pconnect()
+	{
+		log_message('debug', 'SQLite3 doesn\'t support persistent connections');
+		return $this->db_pconnect();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Database version number
+	 *
+	 * @return	string
+	 */
+	public function version()
+	{
+		if (isset($this->data_cache['version']))
+		{
+			return $this->data_cache['version'];
+		}
+
+		$version = $this->conn_id->version();
+		return $this->data_cache['version'] = $version['versionString'];
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Execute the query
+	 *
+	 * @param	string	an SQL query
+	 * @return	mixed	SQLite3Result object or bool
+	 */
+	protected function _execute($sql)
+	{
+		// TODO: Implement use of SQLite3::querySingle(), if needed
+
+		return $this->is_write_type($sql)
+			? $this->conn_id->exec($sql)
+			: $this->conn_id->query($sql);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Begin Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_begin($test_mode = FALSE)
+	{
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ( ! $this->trans_enabled OR $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);
+
+		return $this->conn_id->exec('BEGIN TRANSACTION');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Commit Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_commit()
+	{
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ( ! $this->trans_enabled OR $this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		return $this->conn_id->exec('END TRANSACTION');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rollback Transaction
+	 *
+	 * @return	bool
+	 */
+	public function trans_rollback()
+	{
+		// When transactions are nested we only begin/commit/rollback the outermost ones
+		if ( ! $this->trans_enabled OR $this->_trans_depth > 0)
+		{
+			return TRUE;
+		}
+
+		return $this->conn_id->exec('ROLLBACK');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape String
+	 *
+	 * @param	string
+	 * @param	bool	whether or not the string will be used in a LIKE condition
+	 * @return	string
+	 */
+	public function escape_str($str, $like = FALSE)
+	{
+		if (is_array($str))
+		{
+			foreach ($str as $key => $val)
+			{
+				$str[$key] = $this->escape_str($val, $like);
+			}
+
+			return $str;
+		}
+
+		$str = $this->conn_id->escapeString(remove_invisible_characters($str));
+
+		// escape LIKE condition wildcards
+		if ($like === TRUE)
+		{
+			return str_replace(array($this->_like_escape_chr, '%', '_'),
+						array($this->_like_escape_chr.$this->_like_escape_chr, $this->_like_escape_chr.'%', $this->_like_escape_chr.'_'),
+						$str);
+		}
+
+		return $str;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Affected Rows
+	 *
+	 * @return	int
+	 */
+	public function affected_rows()
+	{
+		return $this->conn_id->changes();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Insert ID
+	 *
+	 * @return	int
+	 */
+	public function insert_id()
+	{
+		return $this->conn_id->lastInsertRowID();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * "Count All" query
+	 *
+	 * Generates a platform-specific query string that counts all records in
+	 * the specified database
+	 *
+	 * @param	string
+	 * @return	int
+	 */
+	public function count_all($table = '')
+	{
+		if ($table == '')
+		{
+			return 0;
+		}
+
+		$result = $this->conn_id->querySingle($this->_count_string.$this->protect_identifiers('numrows')
+							.' FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE));
+
+		return empty($result) ? 0 : (int) $result;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show table query
+	 *
+	 * Generates a platform-specific query string so that the table names can be fetched
+	 *
+	 * @param	bool
+	 * @return	string
+	 */
+	protected function _list_tables($prefix_limit = FALSE)
+	{
+		return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
+			.(($prefix_limit !== FALSE && $this->dbprefix != '')
+				? ' AND "NAME" LIKE \''.$this->escape_like_str($this->dbprefix).'%\' '.sprintf($this->_like_escape_str, $this->_like_escape_chr)
+				: '');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Show column query
+	 *
+	 * Generates a platform-specific query string so that the column names can be fetched
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _list_columns($table = '')
+	{
+		// Not supported
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data query
+	 *
+	 * Generates a platform-specific query so that the column data can be retrieved
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _field_data($table)
+	{
+		return 'SELECT * FROM '.$table.' LIMIT 0,1';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message string
+	 *
+	 * @return	string
+	 */
+	protected function _error_message()
+	{
+		return $this->conn_id->lastErrorMsg();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * The error message number
+	 *
+	 * @return	int
+	 */
+	protected function _error_number()
+	{
+		return $this->conn_id->lastErrorCode();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Escape the SQL Identifiers
+	 *
+	 * This function escapes column and table names
+	 *
+	 * @param	string
+	 * @return	string
+	 */
+	public function _escape_identifiers($item)
+	{
+		if ($this->_escape_char == '')
+		{
+			return $item;
+		}
+
+		foreach ($this->_reserved_identifiers as $id)
+		{
+			if (strpos($item, '.'.$id) !== FALSE)
+			{
+				$item = str_replace('.', $this->_escape_char.'.', $item);
+
+				// remove duplicates if the user already included the escape
+				return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $this->_escape_char.$item);
+			}
+		}
+
+		if (strpos($item, '.') !== FALSE)
+		{
+			$item = str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item);
+		}
+
+		// remove duplicates if the user already included the escape
+		return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $this->_escape_char.$item.$this->_escape_char);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * From Tables
+	 *
+	 * This function implicitly groups FROM tables so there is no confusion
+	 * about operator precedence in harmony with SQL standards
+	 *
+	 * @param	string
+	 * @return	string
+	 */
+	protected 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
+	 *
+	 * @param	string	the table name
+	 * @param	array	the insert keys
+	 * @param	array	the insert values
+	 * @return	string
+	 */
+	protected function _insert($table, $keys, $values)
+	{
+		return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @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
+	 */
+	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE)
+	{
+		foreach ($values as $key => $val)
+		{
+			$valstr[] = $key.' = '.$val;
+		}
+
+		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
+			.(($where != '' && count($where) > 0) ? ' WHERE '.implode(' ', $where) : '')
+			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '')
+			.( ! $limit ? '' : ' LIMIT '.$limit);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Truncate statement
+	 *
+	 * Generates a platform-specific truncate string from the supplied data
+	 * If the database does not support the truncate() command, then
+	 * this method maps to "DELETE FROM table"
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _truncate($table)
+	{
+		return $this->_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @param	array	the where clause
+	 * @param	string	the limit clause
+	 * @return	string
+	 */
+	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	{
+		$conditions = '';
+		if (count($where) > 0 OR count($like) > 0)
+		{
+			$conditions .= "\nWHERE ".implode("\n", $this->ar_where);
+
+			if (count($where) > 0 && count($like) > 0)
+			{
+				$conditions .= ' AND ';
+			}
+			$conditions .= implode("\n", $like);
+		}
+
+		return 'DELETE FROM '.$table.$conditions.( ! $limit ? '' : ' LIMIT '.$limit);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Limit string
+	 *
+	 * Generates a platform-specific LIMIT clause
+	 *
+	 * @param	string	the sql query string
+	 * @param	int	the number of rows to limit the query to
+	 * @param	int	the offset value
+	 * @return	string
+	 */
+	protected function _limit($sql, $limit, $offset)
+	{
+		return $sql.' LIMIT '.($offset ? $offset.',' : '').$limit;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Close DB Connection
+	 *
+	 * @return	void
+	 */
+	protected function _close()
+	{
+		$this->conn_id->close();
+	}
+
+}
+
+/* End of file sqlite3_driver.php */
+/* Location: ./system/database/drivers/sqlite3/sqlite3_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlite3/sqlite3_forge.php b/system/database/drivers/sqlite3/sqlite3_forge.php
new file mode 100644
index 0000000..254db21
--- /dev/null
+++ b/system/database/drivers/sqlite3/sqlite3_forge.php
@@ -0,0 +1,223 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license	http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link	http://codeigniter.com
+ * @since	Version 1.0
+ * @filesource
+ */
+
+/**
+ * SQLite3 Forge Class
+ *
+ * @category	Database
+ * @author	Andrey Andreev
+ * @link	http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlite3_forge extends CI_DB_forge {
+
+	/**
+	 * Create database
+	 *
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	public function _create_database()
+	{
+		// In SQLite, a database is created when you connect to the database.
+		// We'll return TRUE so that an error isn't generated
+		return TRUE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop database
+	 *
+	 * @param	string	the database name
+	 * @return	bool
+	 */
+	public function _drop_database($name)
+	{
+		// In SQLite, a database is dropped when we delete a file
+		if (@file_exists($this->db->database))
+		{
+			// We need to close the pseudo-connection first
+			$this->db->close();
+			if ( ! @unlink($this->db->database))
+			{
+				return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE;
+			}
+
+			return TRUE;
+		}
+
+		return $this->db->db_debug ? $this->db->display_error('db_unable_to_drop') : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Create Table
+	 *
+	 * @param	string	the table name
+	 * @param	array	the fields
+	 * @param	mixed	primary key(s)
+	 * @param	mixed	key(s)
+	 * @param	bool	should 'IF NOT EXISTS' be added to the SQL
+	 * @return	bool
+	 */
+	public function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists)
+	{
+		$sql = 'CREATE TABLE ';
+
+		// IF NOT EXISTS added to SQLite in 3.3.0
+		if ($if_not_exists === TRUE && version_compare($this->db->version(), '3.3.0', '>=') === TRUE)
+		{
+			$sql .= 'IF NOT EXISTS ';
+		}
+
+		$sql .= $this->db->_escape_identifiers($table).'(';
+		$current_field_count = 0;
+
+		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)
+					.' '.$attributes['TYPE']
+					.( ! empty($attributes['CONSTRAINT']) ? '('.$attributes['CONSTRAINT'].')' : '')
+					.(( ! empty($attributes['UNSIGNED']) && $attributes['UNSIGNED'] === TRUE) ? ' UNSIGNED' : '')
+					.(isset($attributes['DEFAULT']) ? ' DEFAULT \''.$attributes['DEFAULT'].'\'' : '')
+					.(( ! empty($attributes['NULL']) && $attributes['NULL'] === TRUE) ? ' NULL' : ' NOT NULL')
+					.(( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE) ? ' AUTO_INCREMENT' : '');
+			}
+
+			// don't add a comma on the end of the last field
+			if (++$current_field_count < count($fields))
+			{
+				$sql .= ',';
+			}
+		}
+
+		if (count($primary_keys) > 0)
+		{
+			$primary_keys = $this->db->protect_identifiers($primary_keys);
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $primary_keys).')';
+		}
+
+		if (is_array($keys) && count($keys) > 0)
+		{
+			foreach ($keys as $key)
+			{
+				if (is_array($key))
+				{
+					$key = $this->db->protect_identifiers($key);
+				}
+				else
+				{
+					$key = array($this->db->protect_identifiers($key));
+				}
+
+				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
+			}
+		}
+
+		return $sql."\n)";
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Drop Table
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	public function _drop_table($table)
+	{
+		return 'DROP TABLE '.$table.' IF EXISTS';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Alter table query
+	 *
+	 * Generates a platform-specific query so that a table can be altered
+	 * Called by add_column(), drop_column(), and column_alter(),
+	 *
+	 * @param	string	the ALTER type (ADD, DROP, CHANGE)
+	 * @param	string	the column name
+	 * @param	string	the table name
+	 * @param	string	the column definition
+	 * @param	string	the default value
+	 * @param	bool	should 'NOT NULL' be added
+	 * @param	string	the field after which we should add the new field
+	 * @return	string
+	 */
+	public function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
+	{
+		/* SQLite only supports adding new columns and it does
+		 * NOT support the AFTER statement. Each new column will
+		 * be added as the last one in the table.
+		 */
+		if ($alter_type !== 'ADD COLUMN')
+		{
+			// Not supported
+			return FALSE;
+		}
+
+		return 'ALTER TABLE '.$this->db->protect_identifiers($table).' '.$alter_type.' '.$this->db->protect_identifiers($column_name)
+			.' '.$column_definition
+			.($default_value != '' ? ' DEFAULT '.$default_value : '')
+			// If NOT NULL is specified, the field must have a DEFAULT value other than NULL
+			.(($null !== NULL && $default_value !== 'NULL') ? ' NOT NULL' : ' NULL');
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Rename a table
+	 *
+	 * Generates a platform-specific query so that a table can be renamed
+	 *
+	 * @param	string	the old table name
+	 * @param	string	the new table name
+	 * @return	string
+	 */
+	public function _rename_table($table_name, $new_table_name)
+	{
+		return 'ALTER TABLE '.$this->db->protect_identifiers($table_name).' RENAME TO '.$this->db->protect_identifiers($new_table_name);
+	}
+}
+
+/* End of file sqlite3_forge.php */
+/* Location: ./system/database/drivers/sqlite3/sqlite3_forge.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlite3/sqlite3_result.php b/system/database/drivers/sqlite3/sqlite3_result.php
new file mode 100644
index 0000000..ddf59db
--- /dev/null
+++ b/system/database/drivers/sqlite3/sqlite3_result.php
@@ -0,0 +1,619 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license	http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link	http://codeigniter.com
+ * @since	Version 1.0
+ * @filesource
+ */
+
+/**
+ * SQLite Result Class
+ *
+ * This class extends the parent result class: CI_DB_result
+ *
+ * @category	Database
+ * @author	Andrey Andreev
+ * @link	http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlite3_result extends CI_DB_result {
+
+	// Overwriting the parent here, so we have a way to know if it's already set
+	public $num_rows;
+
+	// num_fields() might be called multiple times, so we'll use this one to cache it's result
+	protected $_num_fields;
+
+	/**
+	 * Number of rows in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_rows()
+	{
+		/* The SQLite3 driver doesn't have a graceful way to do this,
+		 * so we'll have to do it on our own.
+		 */
+		return is_int($this->num_rows)
+			? $this->num_rows
+			: $this->num_rows = count($this->result_array());
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Number of fields in the result set
+	 *
+	 * @return	int
+	 */
+	public function num_fields()
+	{
+		return ( ! is_int($this->_num_fields))
+			? $this->_num_fields = $this->result_id->numColumns()
+			: $this->_num_fields;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Fetch Field Names
+	 *
+	 * Generates an array of column names
+	 *
+	 * @return	array
+	 */
+	public function list_fields()
+	{
+		$field_names = array();
+		for ($i = 0, $c = $this->num_fields(); $i < $c; $i++)
+		{
+			$field_names[] = $this->result_id->columnName($i);
+		}
+
+		return $field_names;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Field data
+	 *
+	 * Generates an array of objects containing field meta-data
+	 *
+	 * @return	array
+	 */
+	public function field_data()
+	{
+		$retval = array();
+		for ($i = 0, $c = $this->num_fields(); $i < $this->num_fields(); $i++)
+		{
+			$retval[$i]			= new stdClass();
+			$retval[$i]->name		= $this->result_id->columnName($i);
+			$retval[$i]->type		= 'varchar';
+			$retval[$i]->max_length		= 0;
+			$retval[$i]->primary_key	= 0;
+			$retval[$i]->default		= '';
+		}
+
+		return $retval;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Free the result
+	 *
+	 * @return	void
+	 */
+	public function free_result()
+	{
+		if (is_object($this->result_id))
+		{
+			$this->result_id->finalize();
+			$this->result_id = NULL;
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - associative array
+	 *
+	 * Returns the result set as an array
+	 *
+	 * @return	array
+	 */
+	protected function _fetch_assoc()
+	{
+		return $this->result_id->fetchArray(SQLITE3_ASSOC);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Result - object
+	 *
+	 * Returns the result set as an object
+	 *
+	 * @return	object
+	 */
+	protected function _fetch_object()
+	{
+		// No native support for fetching as an object
+		$row = $this->_fetch_assoc();
+		return ($row !== FALSE) ? (object) $row : FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. "array" version.
+	 *
+	 * return	array
+	 */
+	public function result_array()
+	{
+		if (count($this->result_array) > 0)
+		{
+			return $this->result_array;
+		}
+		elseif (is_array($this->row_data))
+		{
+			if (count($this->row_data) === 0)
+			{
+				return $this->result_array;
+			}
+			else
+			{
+				$row_index = count($this->row_data);
+			}
+		}
+		else
+		{
+			$row_index = 0;
+			$this->row_data = array();
+		}
+
+		$row = NULL;
+		while ($row = $this->_fetch_assoc())
+		{
+			$this->row_data[$row_index++] = $row;
+		}
+
+		// Un-comment the following line, in case it becomes needed
+		// $this->_data_seek();
+		return $this->result_array = $this->row_data;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. "object" version.
+	 *
+	 * @return	array
+	 */
+	public function result_object()
+	{
+		if (count($this->result_object) > 0)
+		{
+			return $this->result_object;
+		}
+		elseif (count($this->result_array) > 0)
+		{
+			for ($i = 0, $c = count($this->result_array); $i < $c; $i++)
+			{
+				$this->result_object[] = (object) $this->result_array[$i];
+			}
+
+			return $this->result_object;
+		}
+		elseif (is_array($this->row_data))
+		{
+			if (count($this->row_data) === 0)
+			{
+				return $this->result_object;
+			}
+			else
+			{
+				$row_index = count($this->row_data);
+				for ($i = 0; $i < $row_index; $i++)
+				{
+					$this->result_object[$i] = (object) $this->row_data[$i];
+				}
+			}
+		}
+		else
+		{
+			$row_index = 0;
+			$this->row_data = array();
+		}
+
+		$row = NULL;
+		while ($row = $this->_fetch_assoc())
+		{
+			$this->row_data[$row_index] = $row;
+			$this->result_object[$row_index++] = (object) $row;
+		}
+
+		$this->result_array = $this->row_data;
+
+		/* As described for the num_rows() method - there's no easy
+		 * way to get the number of rows selected. Our work-around
+		 * solution (as in here as well) first checks if result_array
+		 * exists and returns its count. It doesn't however check for
+		 * custom_object_result, so - do it here.
+		 */
+		if ( ! is_int($this->num_rows))
+		{
+			$this->num_rows = count($this->result_object);
+		}
+
+		// Un-comment the following line, in case it becomes needed
+		// $this->_data_seek();
+		return $this->result_object;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Query result. Custom object version.
+	 *
+	 * @param	string	class name used to instantiate rows to
+	 * @return	array
+	 */
+	public function custom_result_object($class_name)
+	{
+		if (array_key_exists($class_name, $this->custom_result_object))
+		{
+			return $this->custom_result_object[$class_name];
+		}
+
+		if ( ! class_exists($class_name) OR ! is_object($this->result_id) OR $this->num_rows() === 0)
+		{
+			return array();
+		}
+
+		/* Even if result_array hasn't been set prior to custom_result_object being called,
+		 * num_rows() has done it.
+		*/
+		$data = &$this->result_array;
+
+		$result_object = array();
+		for ($i = 0, $c = count($data); $i < $c; $i++)
+		{
+			$result_object[$i] = new $class_name();
+			foreach ($data[$i] as $key => $value)
+			{
+				$result_object[$i]->$key = $value;
+			}
+		}
+
+		/* As described for the num_rows() method - there's no easy
+		 * way to get the number of rows selected. Our work-around
+		 * solution (as in here as well) first checks if result_array
+		 * exists and returns its count. It doesn't however check for
+		 * custom_object_result, so - do it here.
+		 */
+		if ( ! is_int($this->num_rows))
+		{
+			$this->num_rows = count($result_object);
+		}
+
+		// Cache and return the array
+		return $this->custom_result_object[$class_name] = $result_object;
+        }
+
+	// --------------------------------------------------------------------
+
+	/* Single row result.
+	 *
+	 * Acts as a wrapper for row_object(), row_array()
+	 * and custom_row_object(). Also used by first_row(), next_row()
+	 * and previous_row().
+	 *
+	 * @param	int	row index
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function row($n = 0, $type = 'object')
+	{
+		if ($type === 'object')
+		{
+			return $this->row_object($n);
+		}
+		elseif ($type === 'array')
+		{
+			return $this->row_array($n);
+		}
+
+		return $this->custom_row_object($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Array version.
+	 *
+	 * @param	int	row index
+	 * @return	array
+	 */
+	public function row_array($n = 0)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+
+		/* If row_data is initialized, it means that we've already tried
+		 * (at least) to fetch some data, so ... check if we already have
+		 * this row.
+		*/
+		if (is_array($this->row_data))
+		{
+			/* If we already have row_data[$n] - return it.
+			 *
+			 * If we enter the elseif, there's a number of reasons to
+			 * return an empty array:
+			 *
+			 *	- count($this->row_data) === 0 means there are no results
+			 *	- num_rows being set or result_array having count() > 0 means
+			 *	  that we've already fetched all data and $n is greater than
+			 *	  our highest row index available
+			 *	- $n < $this->current_row means that if such row existed,
+			 *	  we would've already returned it, therefore $n is an
+			 *	  invalid index
+			 */
+			if (isset($this->row_data[$n])) // We already have this row
+			{
+				$this->current_row = $n;
+				return $this->row_data[$n];
+			}
+			elseif (count($this->row_data) === 0 OR is_int($this->num_rows)
+				OR count($this->result_array) > 0 OR $n < $this->current_row)
+			{
+				// No such row exists
+				return array();
+			}
+
+			// Get the next row index that would actually need to be fetched
+			$current_row = ($this->current_row < count($this->row_data)) ? count($this->row_data) : $this->current_row + 1;
+		}
+		else
+		{
+			$current_row = $this->current_row = 0;
+			$this->row_data = array();
+		}
+
+		/* Fetch more data, if available
+		 *
+		 * NOTE: Operator precedence is important here, if you change
+		 *	 'AND' with '&&' - it WILL BREAK the results, as
+		 *	 $row will be assigned the scalar value of both
+		 *	 expressions!
+		 */
+		while ($row = $this->_fetch_assoc() AND $current_row <= $n)
+		{
+			$this->row_data[$current_row++] = $row;
+		}
+
+		// This would mean that there's no (more) data to fetch
+		if ( ! is_array($this->row_data) OR ! isset($this->row_data[$n]))
+		{
+			// Cache what we already have
+			if (is_array($this->row_data))
+			{
+				$this->num_rows = count($this->row_data);
+				/* Usually, row_data could have less elements than result_array,
+				 * but at this point - they should be exactly the same.
+				 */
+				$this->result_array = $this->row_data;
+			}
+			else
+			{
+				$this->num_rows = 0;
+			}
+
+			return array();
+		}
+
+		$this->current_row = $n;
+		return $this->row_data[$n];
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Object version.
+	 *
+	 * @param	int	row index
+	 * @return	mixed	object if row found; empty array if not
+	 */
+	public function row_object($n = 0)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+
+		/* Logic here is exactly the same as in row_array,
+		 * except we have to cast row_data[$n] to an object.
+		 *
+		 * If we already have result_object though - we can
+		 * directly return from it.
+		 */
+		if (isset($this->result_object[$n]))
+		{
+			$this->current_row = $n;
+			return $this->result_object[$n];
+		}
+
+		$row = $this->row_array($n);
+		// Cast only if the row exists
+		if (count($row) > 0)
+		{
+			$this->current_row = $n;
+			return (object) $row;
+		}
+
+		return array();
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Single row result. Custom object version.
+	 *
+	 * @param	int	row index
+	 * @param	string	custom class name
+	 * @return	mixed	custom object if row found; empty array otherwise
+	 */
+	public function custom_row_object($n = 0, $class_name)
+	{
+		// Make sure $n is not a string
+		if ( ! is_int($n))
+		{
+			$n = (int) $n;
+		}
+
+		if (array_key_exists($class_name, $this->custom_result_object))
+		{
+			/* We already have a the whole result set with this class_name,
+			 * return the specified row if it exists, and an empty array if
+			 * it doesn't.
+			 */
+			if (isset($this->custom_result_object[$class_name][$n]))
+			{
+				$this->current_row = $n;
+				return $this->custom_result_object[$class_name][$n];
+			}
+			else
+			{
+				return array();
+			}
+		}
+		elseif ( ! class_exists($class_name)) // No such class exists
+		{
+			return array();
+		}
+
+		$row = $this->row_array($n);
+		// An array would mean that the row doesn't exist
+		if (is_array($row))
+		{
+			return $row;
+		}
+
+		// Convert to the desired class and return
+		$row_object = new $class_name();
+		foreach ($row as $key => $value)
+		{
+			$row_object->$key = $value;
+		}
+
+		$this->current_row = $n;
+		return $row_object;
+	}
+
+	// --------------------------------------------------------------------
+
+	/* First row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function first_row($type = 'object')
+	{
+		return $this->row(0, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Last row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function last_row($type = 'object')
+	{
+		$result = &$this->result($type);
+		if ( ! isset($this->num_rows))
+		{
+			$this->num_rows = count($result);
+		}
+		$this->current_row = $this->num_rows - 1;
+		return $result[$this->current_row];
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Next row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function next_row($type = 'object')
+	{
+		if (is_array($this->row_data))
+		{
+			$count = count($this->row_data);
+			$n = ($this->current_row > $count OR ($this->current_row === 0 && $count === 0)) ? $count : $this->current_row + 1;
+		}
+		else
+		{
+			$n = 0;
+		}
+
+		return $this->row($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/* Previous row result.
+	 *
+	 * @param	string	('object', 'array' or a custom class name)
+	 * @return	mixed	whatever was passed to the second parameter
+	 */
+	public function previous_row($type = 'object')
+	{
+		$n = ($this->current_row !== 0) ? $this->current_row - 1 : 0;
+		return $this->row($n, $type);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * 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
+	 *
+	 * @return	array
+	 */
+	protected function _data_seek($n = 0)
+	{
+		// Only resetting to the start of the result set is supported
+		return $this->result_id->reset();
+	}
+
+}
+
+/* End of file sqlite3_result.php */
+/* Location: ./system/database/drivers/sqlite3/sqlite3_result.php */
\ No newline at end of file
diff --git a/system/database/drivers/sqlite3/sqlite3_utility.php b/system/database/drivers/sqlite3/sqlite3_utility.php
new file mode 100644
index 0000000..a4dc875
--- /dev/null
+++ b/system/database/drivers/sqlite3/sqlite3_utility.php
@@ -0,0 +1,103 @@
+<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP 5.1.6 or newer
+ *
+ * NOTICE OF LICENSE
+ *
+ * Licensed under the Open Software License version 3.0
+ *
+ * This source file is subject to the Open Software License (OSL 3.0) that is
+ * bundled with this package in the files license.txt / license.rst.  It is
+ * also available through the world wide web at this URL:
+ * http://opensource.org/licenses/OSL-3.0
+ * If you did not receive a copy of the license and are unable to obtain it
+ * through the world wide web, please send an email to
+ * licensing@ellislab.com so we can send you a copy immediately.
+ *
+ * @package	CodeIgniter
+ * @author	EllisLab Dev Team
+ * @copyright	Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/)
+ * @license	http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
+ * @link	http://codeigniter.com
+ * @since	Version 1.0
+ * @filesource
+ */
+
+/**
+ * SQLite3 Utility Class
+ *
+ * @category	Database
+ * @author	Andrey Andreev
+ * @link	http://codeigniter.com/user_guide/database/
+ */
+class CI_DB_sqlite3_utility extends CI_DB_utility {
+
+	/**
+	 * List databases
+	 *
+	 * @return	bool
+	 */
+	public function _list_databases()
+	{
+		// Not supported
+		return FALSE;
+
+		// Do we use this?
+		if ($this->db_debug)
+		{
+			return $this->db->display_error('db_unsuported_feature');
+		}
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Optimize table query
+	 *
+	 * Is optimization even supported in SQLite?
+	 *
+	 * @param	string	the table name
+	 * @return	bool
+	 */
+	public function _optimize_table($table)
+	{
+		// Not supported
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Repair table query
+	 *
+	 * Are table repairs even supported in SQLite?
+	 *
+	 * @param	string	the table name
+	 * @return	object
+	 */
+	public function _repair_table($table)
+	{
+		// Not supported
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * SQLite Export
+	 *
+	 * @param	array	Preferences
+	 * @return	mixed
+	 */
+	public function _backup($params = array())
+	{
+		// Not supported
+		return $this->db->display_error('db_unsuported_feature');
+	}
+
+}
+
+/* End of file sqlite3_utility.php */
+/* Location: ./system/database/drivers/sqlite3/sqlite3_utility.php */
\ No newline at end of file
diff --git a/system/libraries/Cache/drivers/Cache_memcached.php b/system/libraries/Cache/drivers/Cache_memcached.php
index 1028c8f..4cd5f3d 100644
--- a/system/libraries/Cache/drivers/Cache_memcached.php
+++ b/system/libraries/Cache/drivers/Cache_memcached.php
@@ -189,17 +189,17 @@
 		{
 			if ( ! array_key_exists('hostname', $cache_server))
 			{
-				$cache_server['hostname'] = $this->_default_options['default_host'];
+				$cache_server['hostname'] = $this->_memcache_conf['default']['default_host'];
 			}
 
 			if ( ! array_key_exists('port', $cache_server))
 			{
-				$cache_server['port'] = $this->_default_options['default_port'];
+				$cache_server['port'] = $this->_memcache_conf['default']['default_port'];
 			}
 
 			if ( ! array_key_exists('weight', $cache_server))
 			{
-				$cache_server['weight'] = $this->_default_options['default_weight'];
+				$cache_server['weight'] = $this->_memcache_conf['default']['default_weight'];
 			}
 
 			if (get_class($this->_memcached) == 'Memcache')
diff --git a/system/libraries/Email.php b/system/libraries/Email.php
index 8f383c9..48c3bf3 100644
--- a/system/libraries/Email.php
+++ b/system/libraries/Email.php
@@ -407,11 +407,11 @@
 	 * @param	string
 	 * @return	object
 	 */
-	public function attach($filename, $disposition = '', $newname = NULL)
+	public function attach($filename, $disposition = '', $newname = NULL, $mime = '')
 	{
 		$this->_attach_name[] = array($filename, $newname);
-		$this->_attach_type[] = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION));
 		$this->_attach_disp[] = empty($disposition) ? 'attachment' : $disposition; // Can also be 'inline'  Not sure if it matters
+		$this->_attach_type[] = $mime;
 		return $this;
 	}
 
@@ -1049,29 +1049,39 @@
 			$filename = $this->_attach_name[$i][0];
 			$basename = (is_null($this->_attach_name[$i][1])) ? basename($filename) : $this->_attach_name[$i][1];
 			$ctype = $this->_attach_type[$i];
+			$file_content = '';
 
-			if ( ! file_exists($filename))
+			if ($this->_attach_type[$i] == '')
 			{
-				$this->_set_error_message('lang:email_attachment_missing', $filename);
-				return FALSE;
-			}
+				if ( ! file_exists($filename))
+				{
+					$this->_set_error_message('lang:email_attachment_missing', $filename);
+					return FALSE;
+				}
 
+				$file = filesize($filename) +1;
+
+				if ( ! $fp = fopen($filename, FOPEN_READ))
+				{
+					$this->_set_error_message('lang:email_attachment_unreadable', $filename);
+					return FALSE;
+				}
+
+				$ctype = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION));
+				$file_content = fread($fp, $file);
+				fclose($fp);
+			}
+			else
+			{
+				$file_content =& $this->_attach_content[$i];
+			}
 			$attachment[$z++] = "--".$this->_atc_boundary.$this->newline
 				. "Content-type: ".$ctype."; "
 				. "name=\"".$basename."\"".$this->newline
 				. "Content-Disposition: ".$this->_attach_disp[$i].";".$this->newline
 				. "Content-Transfer-Encoding: base64".$this->newline;
 
-			$file = filesize($filename) +1;
-
-			if ( ! $fp = fopen($filename, FOPEN_READ))
-			{
-				$this->_set_error_message('lang:email_attachment_unreadable', $filename);
-				return FALSE;
-			}
-
-			$attachment[$z++] = chunk_split(base64_encode(fread($fp, $file)));
-			fclose($fp);
+			$attachment[$z++] = chunk_split(base64_encode($file_content));
 		}
 
 		$body .= implode($this->newline, $attachment).$this->newline."--".$this->_atc_boundary."--";
diff --git a/tests/codeigniter/database/DB_test.php b/tests/codeigniter/database/DB_test.php
new file mode 100644
index 0000000..9b93e22
--- /dev/null
+++ b/tests/codeigniter/database/DB_test.php
@@ -0,0 +1,49 @@
+<?php
+
+class DB_test extends CI_TestCase {
+
+	// ------------------------------------------------------------------------
+
+	public function test_db_invalid()
+	{
+		$connection = new Mock_Database_DB(array(
+			'undefined' => array(
+				'dsn' => '',
+				'hostname' => 'undefined',
+				'username' => 'undefined',
+				'password' => 'undefined',
+				'database' => 'undefined',
+				'dbdriver' => 'undefined',
+			),
+		));
+
+		$this->setExpectedException('InvalidArgumentException', 'CI Error: Invalid DB driver');
+
+		Mock_Database_DB::DB($connection->set_dsn('undefined'), TRUE);
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function test_db_valid()
+	{
+		$config = Mock_Database_DB::config(DB_DRIVER);
+		$connection = new Mock_Database_DB($config);
+		$db = Mock_Database_DB::DB($connection->set_dsn(DB_DRIVER), TRUE);
+
+		$this->assertTrue($db instanceof CI_DB);
+		$this->assertTrue($db instanceof CI_DB_Driver);
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function test_db_failover()
+	{
+		$config = Mock_Database_DB::config(DB_DRIVER);
+		$connection = new Mock_Database_DB($config);
+		$db = Mock_Database_DB::DB($connection->set_dsn(DB_DRIVER.'_failover'), TRUE);
+
+		$this->assertTrue($db instanceof CI_DB);
+		$this->assertTrue($db instanceof CI_DB_Driver);
+	}
+	
+}
\ No newline at end of file
diff --git a/tests/codeigniter/database/.gitkeep b/tests/codeigniter/database/query_builder/.gitkeep
similarity index 100%
rename from tests/codeigniter/database/.gitkeep
rename to tests/codeigniter/database/query_builder/.gitkeep
diff --git a/tests/mocks/database/ci_test.sqlite b/tests/mocks/database/ci_test.sqlite
new file mode 100755
index 0000000..37ce4f8
--- /dev/null
+++ b/tests/mocks/database/ci_test.sqlite
Binary files differ
diff --git a/tests/mocks/database/config/mysql.php b/tests/mocks/database/config/mysql.php
new file mode 100644
index 0000000..ace0a31
--- /dev/null
+++ b/tests/mocks/database/config/mysql.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+	
+	// Typical Database configuration
+	'mysql' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'travis',
+		'password' => '',
+		'database' => 'ci_test',
+		'dbdriver' => 'mysql',
+	),
+
+	// Database configuration with failover
+	'mysql_failover' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'not_travis',
+		'password' => 'wrong password',
+		'database' => 'not_ci_test',
+		'dbdriver' => 'mysql',
+		'failover' => array(
+			array(
+				'dsn' => '',
+				'hostname' => 'localhost',
+				'username' => 'travis',
+				'password' => '',
+				'database' => 'ci_test',
+				'dbdriver' => 'mysql',
+			),
+		),
+	),
+);
\ No newline at end of file
diff --git a/tests/mocks/database/config/pgsql.php b/tests/mocks/database/config/pgsql.php
new file mode 100644
index 0000000..c06af8c
--- /dev/null
+++ b/tests/mocks/database/config/pgsql.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+	
+	// Typical Database configuration
+	'pgsql' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'postgres',
+		'password' => '',
+		'database' => 'ci_test',
+		'dbdriver' => 'postgre',
+	),
+
+	// Database configuration with failover
+	'pgsql_failover' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'not_travis',
+		'password' => 'wrong password',
+		'database' => 'not_ci_test',
+		'dbdriver' => 'postgre',
+		'failover' => array(
+			array(
+				'dsn' => '',
+				'hostname' => 'localhost',
+				'username' => 'postgres',
+				'password' => '',
+				'database' => 'ci_test',
+				'dbdriver' => 'postgre',
+			),
+		),
+	),
+);
\ No newline at end of file
diff --git a/tests/mocks/database/config/sqlite.php b/tests/mocks/database/config/sqlite.php
new file mode 100644
index 0000000..cf428f4
--- /dev/null
+++ b/tests/mocks/database/config/sqlite.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+	
+	// Typical Database configuration
+	'sqlite' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'sqlite',
+		'password' => 'sqlite',
+		'database' => realpath(__DIR__.'/..').'/ci_test.sqlite',
+		'dbdriver' => 'sqlite',
+	),
+
+	// Database configuration with failover
+	'sqlite_failover' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'sqlite',
+		'password' => 'sqlite',
+		'database' => '../not_exists.sqlite',
+		'dbdriver' => 'sqlite',
+		'failover' => array(
+			array(
+				'dsn' => '',
+				'hostname' => 'localhost',
+				'username' => 'sqlite',
+				'password' => 'sqlite',
+				'database' => realpath(__DIR__.'/..').'/ci_testf.sqlite',
+				'dbdriver' => 'sqlite',
+			),
+		),
+	),
+);
\ No newline at end of file
diff --git a/tests/mocks/database/db.php b/tests/mocks/database/db.php
new file mode 100644
index 0000000..43a0d39
--- /dev/null
+++ b/tests/mocks/database/db.php
@@ -0,0 +1,101 @@
+<?php
+
+class Mock_Database_DB {
+
+	/**
+	 * @var array DB configuration
+	 */
+	private $config = array();
+	
+	/**
+	 * Prepare database configuration skeleton
+	 *
+	 * @param  array 	DB configuration to set
+	 * @return void
+	 */
+	public function __construct($config = array())
+	{
+		$this->config = $config;
+	}
+
+	/**
+	 * Build DSN connection string for DB driver instantiate process
+	 *
+	 * @param 	string 	Group name 		
+	 * @return 	string 	DSN Connection string
+	 */
+	public function set_dsn($group = 'default')
+	{
+		if ( ! isset($this->config[$group]))
+		{
+			throw new InvalidArgumentException('Group '.$group.' not exists');
+		}
+
+		$params = array(
+			'dbprefix' => '',
+			'pconnect' => FALSE,
+			'db_debug' => FALSE,
+			'cache_on' => FALSE,
+			'cachedir' => '',
+			'char_set' => 'utf8',
+			'dbcollat' => 'utf8_general_ci',
+			'swap_pre' => '',
+			'autoinit' => TRUE,
+			'stricton' => FALSE,
+		);
+
+		$config = array_merge($this->config[$group], $params);
+
+		if ( ! empty($config['dsn']))
+		{
+			$dsn = $config['dsn'];
+		}
+		else
+		{
+			$dsn = $config['dbdriver'].'://'.$config['username'].':'.$config['password']
+			       .'@'.$config['hostname'].'/'.$config['database'];
+
+		}
+
+		$other_params = array_slice($config, 6);
+
+		return $dsn.'?'.http_build_query($other_params);
+	}
+
+	/**
+	 * Return a database config array
+	 *
+	 * @see 	./config
+	 * @param 	string 		Driver based configuration
+	 * @return 	array 		
+	 */
+	public static function config($driver)
+	{
+		$dir = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR;
+
+		return include($dir.'config'.DIRECTORY_SEPARATOR.$driver.'.php');
+	}
+
+	/**
+	 * Main DB method wrapper
+	 *
+	 * @param 	string 		Group or DSN string
+	 * @param 	bool 		
+	 * @return 	object 		
+	 */
+	public static function DB($group, $query_builder = FALSE)
+	{
+		include_once(BASEPATH.'database/DB.php');
+
+		try 
+		{
+			$db = DB($group, $query_builder);
+		}
+		catch (Exception $e)
+		{
+			throw new InvalidArgumentException($e->getMessage());
+		}
+
+		return $db;
+	}
+}
\ No newline at end of file
diff --git a/tests/mocks/database/.gitkeep b/tests/mocks/database/schema/.gitkeep
similarity index 100%
rename from tests/mocks/database/.gitkeep
rename to tests/mocks/database/schema/.gitkeep
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
deleted file mode 100644
index dfeecd1..0000000
--- a/tests/phpunit.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<phpunit 
-	bootstrap="Bootstrap.php"
-	colors="true"
-	convertNoticesToExceptions="true"
-	convertWarningsToExceptions="true"
-	stopOnError="false"
-	stopOnFailure="false"
-	stopOnIncomplete="false"
-	stopOnSkipped="false">
-	<testsuites>
-		<testsuite name="CodeIgniter Core Test Suite">
-			<file>./codeigniter/Setup_test.php</file>
-			<directory suffix="test.php">codeigniter/core</directory>
-			<directory suffix="test.php">codeigniter/helpers</directory>
-			<directory suffix="test.php">codeigniter/libraries</directory>
-			<!-- We'll worry about these later ...
-			<directory suffix="test.php">codeigniter/libraries</directory>
-			<directory suffix="test.php">codeigniter/helpers</directory>
-			-->
-		</testsuite>
-	</testsuites>
-	<filters>
-		<blacklist>
-			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
-			<directory suffix=".php">PHP_LIBDIR</directory>
-			<directory suffix=".php">PROJECT_BASE.'tests'</directory>
-			<directory suffix=".php">'../system/core/CodeIgniter.php'</directory>
-		</blacklist>
-		<whitelist>
-			<!--
-			<directory suffix=".php">'../system/core'</directory>
-			-->
-		</whitelist>
-	</filters>
-</phpunit>
\ No newline at end of file
diff --git a/tests/travis/mysql.phpunit.xml b/tests/travis/mysql.phpunit.xml
new file mode 100644
index 0000000..44d6d6e
--- /dev/null
+++ b/tests/travis/mysql.phpunit.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit 
+	bootstrap="../Bootstrap.php"
+	colors="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	stopOnError="false"
+	stopOnFailure="false"
+	stopOnIncomplete="false"
+	stopOnSkipped="false">
+	<php>
+        <const name="DB_DRIVER" value="mysql"/>
+    </php>
+	<testsuites>
+		<testsuite name="CodeIgniter Core Test Suite">
+			<file>../codeigniter/Setup_test.php</file>
+			<directory suffix="test.php">../codeigniter/core</directory>
+			<directory suffix="test.php">../codeigniter/helpers</directory>
+			<directory suffix="test.php">../codeigniter/libraries</directory>
+			<directory suffix="test.php">../codeigniter/database</directory>
+		</testsuite>
+	</testsuites>
+	<filters>
+		<blacklist>
+			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
+			<directory suffix=".php">PHP_LIBDIR</directory>
+			<directory suffix=".php">PROJECT_BASE.'tests'</directory>
+			<directory suffix=".php">'../../system/core/CodeIgniter.php'</directory>
+		</blacklist>
+		<whitelist>
+			<!--
+			<directory suffix=".php">'../system/core'</directory>
+			-->
+		</whitelist>
+	</filters>
+</phpunit>
\ No newline at end of file
diff --git a/tests/travis/pgsql.phpunit.xml b/tests/travis/pgsql.phpunit.xml
new file mode 100644
index 0000000..9f52b40
--- /dev/null
+++ b/tests/travis/pgsql.phpunit.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit 
+	bootstrap="../Bootstrap.php"
+	colors="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	stopOnError="false"
+	stopOnFailure="false"
+	stopOnIncomplete="false"
+	stopOnSkipped="false">
+	<php>
+        <const name="DB_DRIVER" value="pgsql"/>
+    </php>
+	<testsuites>
+		<testsuite name="CodeIgniter Core Test Suite">
+			<file>../codeigniter/Setup_test.php</file>
+			<directory suffix="test.php">../codeigniter/core</directory>
+			<directory suffix="test.php">../codeigniter/helpers</directory>
+			<directory suffix="test.php">../codeigniter/libraries</directory>
+			<directory suffix="test.php">../codeigniter/database</directory>
+		</testsuite>
+	</testsuites>
+	<filters>
+		<blacklist>
+			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
+			<directory suffix=".php">PHP_LIBDIR</directory>
+			<directory suffix=".php">PROJECT_BASE.'tests'</directory>
+			<directory suffix=".php">'../../system/core/CodeIgniter.php'</directory>
+		</blacklist>
+		<whitelist>
+			<!--
+			<directory suffix=".php">'../system/core'</directory>
+			-->
+		</whitelist>
+	</filters>
+</phpunit>
\ No newline at end of file
diff --git a/tests/travis/sqlite.phpunit.xml b/tests/travis/sqlite.phpunit.xml
new file mode 100644
index 0000000..74ebb48
--- /dev/null
+++ b/tests/travis/sqlite.phpunit.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit 
+	bootstrap="../Bootstrap.php"
+	colors="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	stopOnError="false"
+	stopOnFailure="false"
+	stopOnIncomplete="false"
+	stopOnSkipped="false">
+	<php>
+        <const name="DB_DRIVER" value="sqlite"/>
+    </php>
+	<testsuites>
+		<testsuite name="CodeIgniter Core Test Suite">
+			<file>../codeigniter/Setup_test.php</file>
+			<directory suffix="test.php">../codeigniter/core</directory>
+			<directory suffix="test.php">../codeigniter/helpers</directory>
+			<directory suffix="test.php">../codeigniter/libraries</directory>
+			<directory suffix="test.php">../codeigniter/database</directory>
+		</testsuite>
+	</testsuites>
+	<filters>
+		<blacklist>
+			<directory suffix=".php">PEAR_INSTALL_DIR</directory>
+			<directory suffix=".php">PHP_LIBDIR</directory>
+			<directory suffix=".php">PROJECT_BASE.'tests'</directory>
+			<directory suffix=".php">'../../system/core/CodeIgniter.php'</directory>
+		</blacklist>
+		<whitelist>
+			<!--
+			<directory suffix=".php">'../system/core'</directory>
+			-->
+		</whitelist>
+	</filters>
+</phpunit>
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index fed099f..1394baf 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -81,6 +81,7 @@
    -  Added persistent connections support for CUBRID.
    -  Added random ordering support for MSSQL.
    -  Added random ordering support for SQLSRV.
+   -  Added support for SQLite3 database driver.
    -  Improved support of the Oracle (OCI8) driver, including:
 	 -  Added DSN string support (Easy Connect and TNS).
 	 -  Added support for dropping tables to :doc:`Database Forge <database/forge>`.
@@ -93,7 +94,8 @@
 
    -  Added max_filename_increment config setting for Upload library.
    -  CI_Loader::_ci_autoloader() is now a protected method.
-   -  Added custom filename to Email::attach() as $this->email->attach($filename, $disposition, $newname)
+   -  Added custom filename to Email::attach() as $this->email->attach($filename, $disposition, $newname).
+   -  Added possibility to send attachment as buffer string in Email::attach() as $this->email->attach($buffer, $disposition, $newname, $mime).
    -  Cart library changes include:
 	 -  It now auto-increments quantity's instead of just resetting it, this is the default behaviour of large e-commerce sites.
 	 -  Product Name strictness can be disabled via the Cart Library by switching "$product_name_safe"
@@ -181,6 +183,7 @@
 -  Fixed a bug (#940) - csrf_verify() used to set the CSRF cookie while processing a POST request with no actual POST data, which resulted in validating a request that should be considered invalid.
 -  Fixed a bug in PostgreSQL's escape_str() where it didn't properly escape LIKE wild characters.
 -  Fixed a bug in the library loader where some PHP versions wouldn't execute the class constructor.
+-  Fixed a bug (#88) - An unexisting property was used for configuration of the Memcache cache driver.
 
 Version 2.1.1
 =============
diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst
index 040e7e3..3f3bae3 100644
--- a/user_guide_src/source/database/configuration.rst
+++ b/user_guide_src/source/database/configuration.rst
@@ -132,7 +132,7 @@
 **username**		The username used to connect to the database.
 **password**		The password used to connect to the database.
 **database**		The name of the database you want to connect to.
-**dbdriver**		The database type. ie: mysql, postgres, odbc, etc. Must be specified in lower case.
+**dbdriver**		The database type. ie: mysql, postgre, odbc, etc. Must be specified in lower case.
 **dbprefix**		An optional table prefix which will added to the table name when running :doc:
 			`Active Record <active_record>` queries. This permits multiple CodeIgniter installations
 			to share one database.
@@ -166,8 +166,8 @@
 				$db['default']['port'] =  5432;
 ======================  ==================================================================================================
 
-.. note:: Depending on what database platform you are using (MySQL,
-	Postgres, etc.) not all values will be needed. For example, when using
-	SQLite you will not need to supply a username or password, and the
-	database name will be the path to your database file. The information
-	above assumes you are using MySQL.
+.. note:: Depending on what database platform you are using (MySQL, PostgreSQL,
+	etc.) not all values will be needed. For example, when using SQLite you
+	will not need to supply a username or password, and the database name
+	will be the path to your database file. The information above assumes
+	you are using MySQL.
diff --git a/user_guide_src/source/general/requirements.rst b/user_guide_src/source/general/requirements.rst
index 05e8796..d97b7b4 100644
--- a/user_guide_src/source/general/requirements.rst
+++ b/user_guide_src/source/general/requirements.rst
@@ -5,4 +5,4 @@
 -  `PHP <http://www.php.net/>`_ version 5.2.4 or newer.
 -  A Database is required for most web application programming. Current
    supported databases are MySQL (5.1+), MySQLi, MS SQL, SQLSRV, Oracle,
-   PostgreSQL, SQLite, CUBRID, Interbase, ODBC and PDO.
+   PostgreSQL, SQLite, SQLite3, CUBRID, Interbase, ODBC and PDO.
diff --git a/user_guide_src/source/libraries/email.rst b/user_guide_src/source/libraries/email.rst
index d7e40f5..daf0009 100644
--- a/user_guide_src/source/libraries/email.rst
+++ b/user_guide_src/source/libraries/email.rst
@@ -229,11 +229,20 @@
 	$this->email->attach('/path/to/photo2.jpg');
 	$this->email->attach('/path/to/photo3.jpg');
 
-If you'd like to change the disposition or add a custom file name, you can use the second and third paramaters. To use the default disposition (attachment), leave the second parameter blank. Here's an example::
-  
-	$this->email->attach('/path/to/photo1.jpg', 'inline');
-	$this->email->attach('/path/to/photo1.jpg', '', 'birthday.jpg');
-	
+To use the default disposition (attachment), leave the second parameter blank,
+otherwise use a custom disposition::
+
+	$this->email->attach('image.jpg', 'inline');
+
+If you'd like to use a custom file name, you can use the third paramater::
+
+	$this->email->attach('filename.pdf', 'attachment', 'report.pdf');
+
+If you need to use a buffer string instead of a real - physical - file you can
+use the first parameter as buffer, the third parameter as file name and the fourth
+parameter as mime-type::
+
+	$this->email->attach($buffer, 'attachment', 'report.pdf', 'application/pdf');
 
 $this->email->print_debugger()
 -------------------------------