Merge remote-tracking branch 'upstream/develop' into develop-issue-863
diff --git a/application/config/database.php b/application/config/database.php
index 7eac59b..bd68db1 100644
--- a/application/config/database.php
+++ b/application/config/database.php
@@ -37,6 +37,7 @@
 | EXPLANATION OF VARIABLES
 | -------------------------------------------------------------------
 |
+|	['dsn']      The full DSN string describe a connection to the database.
 |	['hostname'] The hostname of your database server.
 |	['username'] The username used to connect to the database
 |	['password'] The password used to connect to the database
@@ -74,6 +75,7 @@
 $active_group = 'default';
 $active_record = TRUE;
 
+$db['default']['dsn']      = '';
 $db['default']['hostname'] = 'localhost';
 $db['default']['username'] = '';
 $db['default']['password'] = '';
diff --git a/readme.rst b/readme.rst
index 26e04ce..2369a8d 100644
--- a/readme.rst
+++ b/readme.rst
@@ -23,7 +23,7 @@
 **************************
 
 You can find a list of all changes for each release in the `user
-guide change log <https://github.com/EllisLab/CodeIgniter/blob/develop/user_guide/changelog.html>`_.
+guide change log <https://github.com/EllisLab/CodeIgniter/blob/develop/user_guide_src/source/changelog.rst>`_.
 
 *******************
 Server Requirements
@@ -193,4 +193,4 @@
 ***************
 
 The EllisLab team and The Reactor Engineers would like to thank all the
-contributors to the CodeIgniter project and you, the CodeIgniter user.
\ No newline at end of file
+contributors to the CodeIgniter project and you, the CodeIgniter user.
diff --git a/system/database/DB.php b/system/database/DB.php
index ed6afd7..d06ffb4 100755
--- a/system/database/DB.php
+++ b/system/database/DB.php
@@ -82,6 +82,7 @@
 		$params = array(
 				'dbdriver'	=> $dns['scheme'],
 				'hostname'	=> (isset($dns['host'])) ? rawurldecode($dns['host']) : '',
+				'port'		=> (isset($dns['port'])) ? rawurldecode($dns['port']) : '',
 				'username'	=> (isset($dns['user'])) ? rawurldecode($dns['user']) : '',
 				'password'	=> (isset($dns['pass'])) ? rawurldecode($dns['pass']) : '',
 				'database'	=> (isset($dns['path'])) ? rawurldecode(substr($dns['path'], 1)) : ''
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 7445a50..b829bbe 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -81,8 +81,7 @@
 	var $stmt_id;
 	var $curs_id;
 	var $limit_used;
-
-
+	
 
 	/**
 	 * Constructor.  Accepts one parameter containing the database
@@ -814,20 +813,23 @@
 
 		if ($query->num_rows() > 0)
 		{
-			foreach ($query->result_array() as $row)
+			$table = FALSE;
+			$rows = $query->result_array();
+			$key = (($row = current($rows)) && in_array('table_name', array_map('strtolower', array_keys($row))));
+
+			if ($key)
 			{
-				if (isset($row['TABLE_NAME']))
-				{
-					$retval[] = $row['TABLE_NAME'];
-				}
-				else
-				{
-					$retval[] = array_shift($row);
-				}
+				$table = array_key_exists('TABLE_NAME', $row) ? 'TABLE_NAME' : 'table_name';
+			}
+
+			foreach ($rows as $row)
+			{
+				$retval[] = ( ! $table) ? current($row) : $row[$table];
 			}
 		}
 
 		$this->data_cache['table_names'] = $retval;
+		
 		return $this->data_cache['table_names'];
 	}
 
@@ -1436,10 +1438,7 @@
 
 		return $item.$alias;
 	}
-
-
 }
 
-
 /* End of file DB_driver.php */
 /* Location: ./system/database/DB_driver.php */
diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php
index 4f4f44b..fc378da 100644
--- a/system/database/drivers/pdo/pdo_driver.php
+++ b/system/database/drivers/pdo/pdo_driver.php
@@ -46,9 +46,10 @@
 
 	// the character used to excape - not necessary for PDO
 	var $_escape_char = '';
+
+	// clause and character used for LIKE escape sequences
 	var $_like_escape_str;
 	var $_like_escape_chr;
-	
 
 	/**
 	 * The syntax to count rows is slightly different across different
@@ -57,29 +58,36 @@
 	 */
 	var $_count_string = "SELECT COUNT(*) AS ";
 	var $_random_keyword;
-	
+
+	// need to track the pdo DSN, driver and options
+	var $dsn;
+	var $pdodriver;
 	var $options = array();
 
 	function __construct($params)
 	{
 		parent::__construct($params);
-		
+
+		if (preg_match('/([^;]+):/', $this->dsn, $match) && count($match) == 2)
+		{
+			// If there is a minimum valid dsn string pattern found, we're done
+			// This is for general PDO users, who tend to have a full DSN string.
+			$this->pdodriver = end($match);
+		}
+		else
+		{
+			// Try to build a complete DSN string from params
+			$this->_connect_string($params);
+		}
+
 		// clause and character used for LIKE escape sequences
-		if (strpos($this->hostname, 'mysql') !== FALSE)
+		// this one depends on the driver being used
+		if ($this->pdodriver == 'mysql')
 		{
 			$this->_like_escape_str = '';
 			$this->_like_escape_chr = '';
-			
-			//Prior to this version, the charset can't be set in the dsn
-			if(is_php('5.3.6'))
-			{
-				$this->hostname .= ";charset={$this->char_set}";
-			}
-			
-			//Set the charset with the connection options
-			$this->options['PDO::MYSQL_ATTR_INIT_COMMAND'] = "SET NAMES {$this->char_set}";
 		}
-		else if (strpos($this->hostname, 'odbc') !== FALSE)
+		elseif ($this->pdodriver == 'odbc')
 		{
 			$this->_like_escape_str = " {escape '%s'} ";
 			$this->_like_escape_chr = '!';
@@ -90,17 +98,88 @@
 			$this->_like_escape_chr = '!';
 		}
 		
-		if (strpos($this->hostname, 'sqlite') === FALSE)
-		{
-			$this->hostname .= ";dbname=".$this->database;
-		}
-		
-		$this->trans_enabled = FALSE;
-
+		$this->trans_enabled   = FALSE;
 		$this->_random_keyword = ' RND('.time().')'; // database specific random keyword
 	}
 
 	/**
+	 * Connection String
+	 *
+	 * @access	private
+	 * @param	array
+	 * @return	void
+	 */
+	function _connect_string($params)
+	{
+		if (strpos($this->hostname, ':'))
+		{
+			// hostname generally would have this prototype
+			// $db['hostname'] = 'pdodriver:host(/Server(/DSN))=hostname(/DSN);';
+			// We need to get the prefix (pdodriver used by PDO).
+			$this->dsn = $this->hostname;
+			$this->pdodriver = substr($this->hostname, 0, strpos($this->hostname, ':'));
+		}
+		else
+		{
+			// Invalid DSN, display an error
+			if ( ! array_key_exists('pdodriver', $params))
+			{
+				show_error('Invalid DB Connection String for PDO');
+			}
+
+			// Assuming that the following DSN string format is used:
+			// $dsn = 'pdo://username:password@hostname:port/database?pdodriver=pgsql';
+			$this->dsn = $this->pdodriver.':';
+
+			// Add hostname to the DSN for databases that need it
+			if ( ! empty($this->hostname) && in_array($this->pdodriver, array('informix', 'mysql', 'pgsql', 'sybase', 'mssql', 'dblib', 'cubrid')))
+			{
+			    $this->dsn .= 'host='.$this->hostname.';';
+			}
+
+			// Add a port to the DSN for databases that can use it
+			if ( ! empty($this->port) && in_array($this->pdodriver, array('informix', 'mysql', 'pgsql', 'ibm', 'cubrid')))
+			{
+			    $this->dsn .= 'port='.$this->port.';';
+			}
+		}
+
+		// Add the database name to the DSN, if needed
+	    if (stripos($this->dsn, 'dbname') === FALSE 
+	       && in_array($this->pdodriver, array('4D', 'pgsql', 'mysql', 'firebird', 'sybase', 'mssql', 'dblib', 'cubrid')))
+	    {
+	        $this->dsn .= 'dbname='.$this->database.';';
+	    }
+	    elseif (stripos($this->dsn, 'database') === FALSE && in_array($this->pdodriver, array('ibm', 'sqlsrv')))
+	    {
+	    	if (stripos($this->dsn, 'dsn') === FALSE)
+	    	{
+		        $this->dsn .= 'database='.$this->database.';';
+	    	}
+	    }
+	    elseif ($this->pdodriver === 'sqlite' && $this->dsn === 'sqlite:')
+	    {
+	        if ($this->database !== ':memory')
+	        {
+	            if ( ! file_exists($this->database))
+	            {
+	                show_error('Invalid DB Connection string for PDO SQLite');
+	            }
+
+	            $this->dsn .= (strpos($this->database, DIRECTORY_SEPARATOR) !== 0) ? DIRECTORY_SEPARATOR : '';
+	        }
+
+	        $this->dsn .= $this->database;
+	    }
+
+	    // Add charset to the DSN, if needed
+	    if ( ! empty($this->char_set) && in_array($this->pdodriver, array('4D', 'mysql', 'sybase', 'mssql', 'dblib', 'oci')))
+	    {
+	        $this->dsn .= 'charset='.$this->char_set.';';
+	    }
+	}
+
+	/**
 	 * Non-persistent database connection
 	 *
 	 * @access	private called by the base class
@@ -108,9 +187,9 @@
 	 */
 	function db_connect()
 	{
-		$this->options['PDO::ATTR_ERRMODE'] = PDO::ERRMODE_SILENT;
-		
-		return new PDO($this->hostname, $this->username, $this->password, $this->options);
+		$this->options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
+
+		return $this->pdo_connect();
 	}
 
 	// --------------------------------------------------------------------
@@ -123,10 +202,44 @@
 	 */
 	function db_pconnect()
 	{
-		$this->options['PDO::ATTR_ERRMODE'] = PDO::ERRMODE_SILENT;
-		$this->options['PDO::ATTR_PERSISTENT'] = TRUE;
+		$this->options[PDO::ATTR_ERRMODE]    = PDO::ERRMODE_SILENT;
+		$this->options[PDO::ATTR_PERSISTENT] = TRUE;
 	
-		return new PDO($this->hostname, $this->username, $this->password, $this->options);
+		return $this->pdo_connect();
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * PDO connection
+	 *
+	 * @access	private called by the PDO driver class
+	 * @return	resource
+	 */
+	function pdo_connect()
+	{
+		// Refer : http://php.net/manual/en/ref.pdo-mysql.connection.php
+		if ($this->pdodriver == 'mysql' && is_php('5.3.6'))
+		{
+			$this->options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES $this->char_set COLLATE '$this->dbcollat'";
+		}
+
+		// Connecting...
+		try 
+		{
+			$db = new PDO($this->dsn, $this->username, $this->password, $this->options);
+		} 
+		catch (PDOException $e) 
+		{
+			if ($this->db_debug && empty($this->failover))
+			{
+				$this->display_error($e->getMessage(), '', TRUE);
+			}
+
+			return FALSE;
+		}
+
+		return $db;
 	}
 
 	// --------------------------------------------------------------------
@@ -146,6 +259,7 @@
 		{
 			return $this->db->display_error('db_unsuported_feature');
 		}
+
 		return FALSE;
 	}
 
@@ -175,7 +289,6 @@
 	 */
 	function db_set_charset($charset, $collation)
 	{
-		// @todo - add support if needed
 		return TRUE;
 	}
 
@@ -204,6 +317,7 @@
 	function _execute($sql)
 	{
 		$sql = $this->_prep_query($sql);
+
 		$result_id = $this->conn_id->query($sql);
 		
 		if (is_object($result_id))
@@ -231,6 +345,17 @@
 	 */
 	function _prep_query($sql)
 	{
+		if ($this->pdodriver === 'pgsql')
+		{
+			// Change the backtick(s) for Postgre
+			$sql = str_replace('`', '"', $sql);
+		}
+		elseif ($this->pdodriver === 'sqlite')
+		{
+			// Change the backtick(s) for SQLite
+			$sql = str_replace('`', '', $sql);
+		}
+
 		return $sql;
 	}
 
@@ -285,6 +410,7 @@
 		}
 
 		$ret = $this->conn->commit();
+		
 		return $ret;
 	}
 
@@ -310,6 +436,7 @@
 		}
 
 		$ret = $this->conn_id->rollBack();
+
 		return $ret;
 	}
 
@@ -348,7 +475,9 @@
 		if ($like === TRUE)
 		{
 			$str = str_replace(	array('%', '_', $this->_like_escape_chr),
-								array($this->_like_escape_chr.'%', $this->_like_escape_chr.'_', $this->_like_escape_chr.$this->_like_escape_chr),
+								array($this->_like_escape_chr.'%', 
+								      $this->_like_escape_chr.'_', 
+								      $this->_like_escape_chr.$this->_like_escape_chr),
 								$str);
 		}
 
@@ -378,9 +507,9 @@
 	 */
 	function insert_id($name=NULL)
 	{
-		//Convenience method for postgres insertid
-		if (strpos($this->hostname, 'pgsql') !== FALSE)
+		if ($this->pdodriver == 'pgsql')
 		{
+			//Convenience method for postgres insertid
 			$v = $this->_version();
 
 			$table	= func_num_args() > 0 ? func_get_arg(0) : NULL;
@@ -389,8 +518,10 @@
 			{
 				$sql='SELECT LASTVAL() as ins_id';
 			}
+
 			$query = $this->query($sql);
-			$row = $query->row();
+			$row   = $query->row();
+
 			return $row->ins_id;
 		}
 		else
@@ -418,7 +549,9 @@
 			return 0;
 		}
 
-		$query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE));
+		$sql   = $this->_count_string.$this->_protect_identifiers('numrows').' FROM ';
+		$sql  .= $this->_protect_identifiers($table, TRUE, NULL, FALSE);
+		$query = $this->query($sql);
 
 		if ($query->num_rows() == 0)
 		{
@@ -427,6 +560,7 @@
 
 		$row = $query->row();
 		$this->_reset_select();
+
 		return (int) $row->numrows;
 	}
 
@@ -443,12 +577,19 @@
 	 */
 	function _list_tables($prefix_limit = FALSE)
 	{
-		$sql = "SHOW TABLES FROM `".$this->database."`";
+		if ($this->pdodriver == 'pgsql')
+		{
+			// Analog function to show all tables in postgre
+			$sql = "SELECT * FROM information_schema.tables WHERE table_schema = 'public'";
+		}
+		else
+		{
+			$sql = "SHOW TABLES FROM `".$this->database."`";
+		}
 
 		if ($prefix_limit !== FALSE AND $this->dbprefix != '')
 		{
-			//$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr);
-			return FALSE; // not currently supported
+			return FALSE; 
 		}
 
 		return $sql;
@@ -467,7 +608,7 @@
 	 */
 	function _list_columns($table = '')
 	{
-		return "SHOW COLUMNS FROM ".$table;
+		return 'SHOW COLUMNS FROM '.$this->_from_tables($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -483,7 +624,7 @@
 	 */
 	function _field_data($table)
 	{
-		return "SELECT TOP 1 FROM ".$table;
+		return 'SELECT TOP 1 FROM '.$this->_from_tables($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -497,6 +638,7 @@
 	function _error_message()
 	{
 		$error_array = $this->conn_id->errorInfo();
+
 		return $error_array[2];
 	}
 
@@ -544,8 +686,8 @@
 
 		if (strpos($item, '.') !== FALSE)
 		{
-			$str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char;
-			
+			$str  = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item);
+			$str .= $this->_escape_char;
 		}
 		else
 		{
@@ -575,7 +717,7 @@
 			$tables = array($tables);
 		}
 
-		return (count($tables) == 1) ? $tables[0] : '('.implode(', ', $tables).')';
+		return (count($tables) == 1) ? '`'.$tables[0].'`' : '('.implode(', ', $tables).')';
 	}
 
 	// --------------------------------------------------------------------
@@ -593,7 +735,7 @@
 	 */
 	function _insert($table, $keys, $values)
 	{
-		return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")";
+		return 'INSERT INTO '.$this->_from_tables($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
 	}
 	
 	// --------------------------------------------------------------------
@@ -611,7 +753,7 @@
 	 */
 	function _insert_batch($table, $keys, $values)
 	{
-		return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values);
+		return 'INSERT INTO '.$this->_from_tables($table).' ('.implode(', ', $keys).') VALUES '.implode(', ', $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -636,14 +778,11 @@
 			$valstr[] = $key." = ".$val;
 		}
 
-		$limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
+		$limit   = ( ! $limit) ? '' : ' LIMIT '.$limit;
+		$orderby = (count($orderby) >= 1) ? ' ORDER BY '.implode(', ', $orderby) : '';
 
-		$orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):'';
-
-		$sql = "UPDATE ".$table." SET ".implode(', ', $valstr);
-
-		$sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : '';
-
+		$sql  = 'UPDATE '.$this->_from_tables($table).' SET '.implode(', ', $valstr);
+		$sql .= ($where != '' && count($where) >= 1) ? ' WHERE '.implode(' ', $where) : '';
 		$sql .= $orderby.$limit;
 
 		return $sql;
@@ -664,8 +803,8 @@
 	 */
 	function _update_batch($table, $values, $index, $where = NULL)
 	{
-		$ids = array();
-		$where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : '';
+		$ids   = array();
+		$where = ($where != '' && count($where) >=1) ? implode(" ", $where).' AND ' : '';
 
 		foreach ($values as $key => $val)
 		{
@@ -680,12 +819,13 @@
 			}
 		}
 
-		$sql = "UPDATE ".$table." SET ";
+		$sql   = 'UPDATE '.$this->_from_tables($table).' SET ';
 		$cases = '';
 
 		foreach ($final as $k => $v)
 		{
 			$cases .= $k.' = CASE '."\n";
+
 			foreach ($v as $row)
 			{
 				$cases .= $row."\n";
@@ -695,7 +835,6 @@
 		}
 
 		$sql .= substr($cases, 0, -2);
-
 		$sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
 
 		return $sql;
@@ -739,19 +878,20 @@
 
 		if (count($where) > 0 OR count($like) > 0)
 		{
-			$conditions = "\nWHERE ";
+			$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;
+		return 'DELETE FROM '.$this->_from_tables($table).$conditions.$limit;
 	}
 
 	// --------------------------------------------------------------------
@@ -769,27 +909,16 @@
 	 */
 	function _limit($sql, $limit, $offset)
 	{
-		if (strpos($this->hostname, 'cubrid') !== FALSE || strpos($this->hostname, 'sqlite') !== FALSE)
+		if ($this->pdodriver == 'cubrid' OR $this->pdodriver == 'sqlite')
 		{
-			if ($offset == 0)
-			{
-				$offset = '';
-			}
-			else
-			{
-				$offset .= ", ";
-			}
+			$offset = ($offset == 0) ? '' : $offset.', ';
 
-			return $sql."LIMIT ".$offset.$limit;
+			return $sql.'LIMIT '.$offset.$limit;
 		}
 		else
 		{
-			$sql .= "LIMIT ".$limit;
-
-			if ($offset > 0)
-			{
-				$sql .= " OFFSET ".$offset;
-			}
+			$sql .= 'LIMIT '.$limit;
+			$sql .= ($offset > 0) ? ' OFFSET '.$offset : '';
 			
 			return $sql;
 		}
@@ -809,10 +938,7 @@
 		$this->conn_id = null;
 	}
 
-
 }
 
-
-
 /* End of file pdo_driver.php */
 /* Location: ./system/database/drivers/pdo/pdo_driver.php */
\ No newline at end of file
diff --git a/system/database/drivers/pdo/pdo_forge.php b/system/database/drivers/pdo/pdo_forge.php
index 0764159..478b2db 100644
--- a/system/database/drivers/pdo/pdo_forge.php
+++ b/system/database/drivers/pdo/pdo_forge.php
@@ -96,7 +96,7 @@
 			$sql .= 'IF NOT EXISTS ';
 		}
 
-		$sql .= $this->db->_escape_identifiers($table)." (";
+		$sql .= '`'.$this->db->_escape_identifiers($table).'` (';
 		$current_field_count = 0;
 
 		foreach ($fields as $field=>$attributes)
@@ -111,6 +111,7 @@
 			else
 			{
 				$attributes = array_change_key_case($attributes, CASE_UPPER);
+				$numeric    = array('SERIAL', 'INTEGER');
 
 				$sql .= "\n\t".$this->db->_protect_identifiers($field);
 
@@ -118,7 +119,11 @@
 
 				if (array_key_exists('CONSTRAINT', $attributes))
 				{
-					$sql .= '('.$attributes['CONSTRAINT'].')';
+					// Exception for Postgre numeric which not too happy with constraint within those type
+					if ( ! ($this->db->pdodriver == 'pgsql' && in_array($attributes['TYPE'], $numeric)))
+					{
+						$sql .= '('.$attributes['CONSTRAINT'].')';
+					}
 				}
 
 				if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE)
@@ -219,7 +224,7 @@
 	 */
 	function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '')
 	{
-		$sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table)." $alter_type ".$this->db->_protect_identifiers($column_name);
+		$sql = 'ALTER TABLE `'.$this->db->_protect_identifiers($table)."` $alter_type ".$this->db->_protect_identifiers($column_name);
 
 		// DROP has everything it needs now.
 		if ($alter_type == 'DROP')
@@ -271,7 +276,6 @@
 		return $sql;
 	}
 
-
 }
 
 /* End of file pdo_forge.php */
diff --git a/system/database/drivers/pdo/pdo_result.php b/system/database/drivers/pdo/pdo_result.php
index 6b523b0..c333abc 100644
--- a/system/database/drivers/pdo/pdo_result.php
+++ b/system/database/drivers/pdo/pdo_result.php
@@ -39,6 +39,16 @@
 class CI_DB_pdo_result extends CI_DB_result {
 
 	/**
+	 * @var bool  Hold the flag whether a result handler already fetched before
+	 */
+	protected $is_fetched = FALSE;
+
+	/**
+	 * @var mixed Hold the fetched assoc array of a result handler
+	 */
+	protected $result_assoc;
+
+	/**
 	 * Number of rows in the result set
 	 *
 	 * @access	public
@@ -46,7 +56,59 @@
 	 */
 	function num_rows()
 	{
-		return $this->result_id->rowCount();
+		if (empty($this->result_id) OR ! is_object($this->result_id))
+		{
+			// invalid result handler
+			return 0;
+		}
+		elseif (($num_rows = $this->result_id->rowCount()) && $num_rows > 0)
+		{
+			// If rowCount return something, we're done.
+			return $num_rows;
+		}
+
+		// Fetch the result, instead perform another extra query
+		return ($this->is_fetched && is_array($this->result_assoc)) ? count($this->result_assoc) : count($this->result_assoc());
+	}
+
+	/**
+	 * Fetch the result handler
+	 *
+	 * @access	public
+	 * @return	mixed
+	 */
+	function result_assoc()
+	{
+		// If the result already fetched before, use that one
+		if (count($this->result_array) > 0 OR $this->is_fetched)
+		{
+			return $this->result_array();
+		}
+
+		// Define the output
+		$output = array('assoc', 'object');
+
+		// Fetch the result
+		foreach ($output as $type)
+		{
+			// Define the method and handler
+			$res_method  = '_fetch_'.$type;
+			$res_handler = 'result_'.$type;
+			
+			$this->$res_handler = array();
+			$this->_data_seek(0);
+
+			while ($row = $this->$res_method())
+			{
+				$this->{$res_handler}[] = $row;
+			}
+		}
+
+		// Save this as buffer and marked the fetch flag
+		$this->result_array = $this->result_assoc;
+		$this->is_fetched = TRUE;
+
+		return $this->result_assoc;
 	}
 
 	// --------------------------------------------------------------------
@@ -78,6 +140,7 @@
 		{
 			return $this->db->display_error('db_unsuported_feature');
 		}
+		
 		return FALSE;
 	}
 
@@ -110,6 +173,7 @@
 			{
 				return $this->db->display_error('db_unsuported_feature');
 			}
+
 			return FALSE;
 		}
 	}
@@ -178,6 +242,5 @@
 
 }
 
-
 /* End of file pdo_result.php */
 /* Location: ./system/database/drivers/pdo/pdo_result.php */
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index a364097..b21c0ab 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -49,6 +49,8 @@
    -  Adding $escape parameter to the order_by function, this enables ordering by custom fields.
    -  MySQLi driver now uses mysqli_get_server_info() for server version checking.
    -  MySQLi driver now supports persistent connections when running on PHP >= 5.3.
+   -  Added dsn if the group connections in the config use PDO or any driver which need DSN.
+   -  Improved PDO database support.
 
 -  Libraries
 
diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst
index 4f88c25..040e7e3 100644
--- a/user_guide_src/source/database/configuration.rst
+++ b/user_guide_src/source/database/configuration.rst
@@ -28,6 +28,10 @@
 	$db['default']['autoinit'] = TRUE;
 	$db['default']['stricton'] = FALSE;
 
+If you use PDO as your dbdriver, you can specify the full DSN string describe a connection to the database like this::
+
+	$db['default']['dsn'] = 'pgsql:host=localhost;port=5432;dbname=database_name';
+
 You can also specify failovers for the situation when the main connection cannot connect for some reason.
 These failovers can be specified by setting the failover for a connection like this::