Merge remote-tracking branch 'upstream/develop' into develop-db-cubrid
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/index.php b/index.php
index 1712a7d..a378266 100644
--- a/index.php
+++ b/index.php
@@ -218,6 +218,7 @@
 	{
 		if ( ! is_dir(BASEPATH.$application_folder.'/'))
 		{
+			header('HTTP/1.1 503 Service Unavailable.', TRUE, '503');
 			exit('Your application folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF);
 		}
 
@@ -233,6 +234,7 @@
 	{
 		if ( ! is_dir(APPPATH.'views/'))
 		{
+			header('HTTP/1.1 503 Service Unavailable.', TRUE, '503');
 			exit('Your view folder path does not appear to be set correctly. Please open the following file and correct this: '.SELF);
 		}
 
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/core/Common.php b/system/core/Common.php
index 2f9c4ff..225227d 100644
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -177,6 +177,7 @@
 		{
 			// Note: We use exit() rather then show_error() in order to avoid a
 			// self-referencing loop with the Excptions class
+			set_status_header(503);
 			exit('Unable to locate the specified class: '.$class.'.php');
 		}
 
@@ -243,6 +244,7 @@
 		// Fetch the config file
 		if ( ! file_exists($file_path))
 		{
+			set_status_header(503);
 			exit('The configuration file does not exist.');
 		}
 
@@ -251,6 +253,7 @@
 		// Does the $config array exist in the file?
 		if ( ! isset($config) OR ! is_array($config))
 		{
+			set_status_header(503);
 			exit('Your config file does not appear to be formatted correctly.');
 		}
 
diff --git a/system/core/Input.php b/system/core/Input.php
index 7a16e51..ee15f40 100755
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -25,8 +25,6 @@
  * @filesource
  */
 
-// ------------------------------------------------------------------------
-
 /**
  * Input Class
  *
@@ -152,7 +150,7 @@
 	public function get($index = NULL, $xss_clean = FALSE)
 	{
 		// Check if a field has been provided
-		if ($index === NULL AND ! empty($_GET))
+		if ($index === NULL && ! empty($_GET))
 		{
 			$get = array();
 
@@ -179,7 +177,7 @@
 	public function post($index = NULL, $xss_clean = FALSE)
 	{
 		// Check if a field has been provided
-		if ($index === NULL AND ! empty($_POST))
+		if ($index === NULL && ! empty($_POST))
 		{
 			$post = array();
 
@@ -206,9 +204,9 @@
 	*/
 	public function get_post($index = '', $xss_clean = FALSE)
 	{
-		return ( ! isset($_POST[$index]))
-			? $this->get($index, $xss_clean)
-			: $this->post($index, $xss_clean);
+		return isset($_POST[$index])
+			? $this->post($index, $xss_clean)
+			: $this->get($index, $xss_clean);
 	}
 
 	// --------------------------------------------------------------------
@@ -256,19 +254,19 @@
 			}
 		}
 
-		if ($prefix == '' AND config_item('cookie_prefix') != '')
+		if ($prefix == '' && config_item('cookie_prefix') != '')
 		{
 			$prefix = config_item('cookie_prefix');
 		}
-		if ($domain == '' AND config_item('cookie_domain') != '')
+		if ($domain == '' && config_item('cookie_domain') != '')
 		{
 			$domain = config_item('cookie_domain');
 		}
-		if ($path == '/' AND config_item('cookie_path') != '/')
+		if ($path == '/' && config_item('cookie_path') !== '/')
 		{
 			$path = config_item('cookie_path');
 		}
-		if ($secure == FALSE AND config_item('cookie_secure') != FALSE)
+		if ($secure == FALSE && config_item('cookie_secure') != FALSE)
 		{
 			$secure = config_item('cookie_secure');
 		}
@@ -320,11 +318,11 @@
 
 			$this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
 		}
-		elseif ( ! $this->server('HTTP_CLIENT_IP') AND $this->server('REMOTE_ADDR'))
+		elseif ( ! $this->server('HTTP_CLIENT_IP') && $this->server('REMOTE_ADDR'))
 		{
 			$this->ip_address = $_SERVER['REMOTE_ADDR'];
 		}
-		elseif ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP'))
+		elseif ($this->server('REMOTE_ADDR') && $this->server('HTTP_CLIENT_IP'))
 		{
 			$this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
 		}
@@ -414,7 +412,7 @@
 			return $this->user_agent;
 		}
 
-		return $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT'];
+		return $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : FALSE;
 	}
 
 	// --------------------------------------------------------------------
@@ -469,19 +467,16 @@
 		{
 			$_GET = array();
 		}
-		else
+		elseif (is_array($_GET) && count($_GET) > 0)
 		{
-			if (is_array($_GET) AND count($_GET) > 0)
+			foreach ($_GET as $key => $val)
 			{
-				foreach ($_GET as $key => $val)
-				{
-					$_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
-				}
+				$_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
 			}
 		}
 
 		// Clean $_POST Data
-		if (is_array($_POST) AND count($_POST) > 0)
+		if (is_array($_POST) && count($_POST) > 0)
 		{
 			foreach ($_POST as $key => $val)
 			{
@@ -490,7 +485,7 @@
 		}
 
 		// Clean $_COOKIE Data
-		if (is_array($_COOKIE) AND count($_COOKIE) > 0)
+		if (is_array($_COOKIE) && count($_COOKIE) > 0)
 		{
 			// Also get rid of specially treated cookies that might be set by a server
 			// or silly application, that are of no use to a CI application anyway
@@ -568,7 +563,7 @@
 		}
 
 		// Standardize newlines if needed
-		if ($this->_standardize_newlines == TRUE AND strpos($str, "\r") !== FALSE)
+		if ($this->_standardize_newlines == TRUE && strpos($str, "\r") !== FALSE)
 		{
 			return str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str);
 		}
@@ -592,6 +587,7 @@
 	{
 		if ( ! preg_match('/^[a-z0-9:_\/-]+$/i', $str))
 		{
+			set_status_header(503);
 			exit('Disallowed Key Characters.');
 		}
 
@@ -624,7 +620,7 @@
 		}
 		else
 		{
-			$headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE');
+			$headers['Content-Type'] = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE');
 
 			foreach ($_SERVER as $key => $val)
 			{
@@ -654,9 +650,9 @@
 	 *
 	 * Returns the value of a single member of the headers class member
 	 *
-	 * @param 	string		array key for $this->headers
-	 * @param	boolean		XSS Clean or not
-	 * @return 	mixed		FALSE on failure, string on success
+	 * @param	string	array key for $this->headers
+	 * @param	bool	XSS Clean or not
+	 * @return	mixed	FALSE on failure, string on success
 	 */
 	public function get_request_header($index, $xss_clean = FALSE)
 	{
@@ -670,12 +666,9 @@
 			return FALSE;
 		}
 
-		if ($xss_clean === TRUE)
-		{
-			return $this->security->xss_clean($this->headers[$index]);
-		}
-
-		return $this->headers[$index];
+		return ($xss_clean === TRUE)
+			? $this->security->xss_clean($this->headers[$index])
+			: $this->headers[$index];
 	}
 
 	// --------------------------------------------------------------------
@@ -685,11 +678,11 @@
 	 *
 	 * Test to see if a request contains the HTTP_X_REQUESTED_WITH header
 	 *
-	 * @return 	boolean
+	 * @return 	bool
 	 */
 	public function is_ajax_request()
 	{
-		return ($this->server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest');
+		return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
 	}
 
 	// --------------------------------------------------------------------
@@ -699,11 +692,11 @@
 	 *
 	 * Test to see if a request was made from the command line
 	 *
-	 * @return 	boolean
+	 * @return 	bool
 	 */
 	public function is_cli_request()
 	{
-		return (php_sapi_name() === 'cli') or defined('STDIN');
+		return (php_sapi_name() === 'cli' OR defined('STDIN'));
 	}
 
 }
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..e84c1aa 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,92 @@
 			$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;
+			$split_dsn = explode(':', $this->hostname);
+			$this->pdodriver = $split_dsn[0];
+			
+			// End this part of the dsn with a semicolon
+			$this->dsn .= rtrim(';', $this->dsn) . ';';
+		}
+		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 +191,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 +206,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 +263,7 @@
 		{
 			return $this->db->display_error('db_unsuported_feature');
 		}
+
 		return FALSE;
 	}
 
@@ -175,7 +293,6 @@
 	 */
 	function db_set_charset($charset, $collation)
 	{
-		// @todo - add support if needed
 		return TRUE;
 	}
 
@@ -204,6 +321,7 @@
 	function _execute($sql)
 	{
 		$sql = $this->_prep_query($sql);
+
 		$result_id = $this->conn_id->query($sql);
 		
 		if (is_object($result_id))
@@ -231,6 +349,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 +414,7 @@
 		}
 
 		$ret = $this->conn->commit();
+		
 		return $ret;
 	}
 
@@ -310,6 +440,7 @@
 		}
 
 		$ret = $this->conn_id->rollBack();
+
 		return $ret;
 	}
 
@@ -348,7 +479,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 +511,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 +522,10 @@
 			{
 				$sql='SELECT LASTVAL() as ins_id';
 			}
+
 			$query = $this->query($sql);
-			$row = $query->row();
+			$row   = $query->row();
+
 			return $row->ins_id;
 		}
 		else
@@ -418,7 +553,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 +564,7 @@
 
 		$row = $query->row();
 		$this->_reset_select();
+
 		return (int) $row->numrows;
 	}
 
@@ -443,12 +581,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 +612,7 @@
 	 */
 	function _list_columns($table = '')
 	{
-		return "SHOW COLUMNS FROM ".$table;
+		return 'SHOW COLUMNS FROM '.$this->_from_tables($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -483,7 +628,7 @@
 	 */
 	function _field_data($table)
 	{
-		return "SELECT TOP 1 FROM ".$table;
+		return 'SELECT TOP 1 FROM '.$this->_from_tables($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -497,6 +642,7 @@
 	function _error_message()
 	{
 		$error_array = $this->conn_id->errorInfo();
+
 		return $error_array[2];
 	}
 
@@ -544,8 +690,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 +721,7 @@
 			$tables = array($tables);
 		}
 
-		return (count($tables) == 1) ? $tables[0] : '('.implode(', ', $tables).')';
+		return (count($tables) == 1) ? '`'.$tables[0].'`' : '('.implode(', ', $tables).')';
 	}
 
 	// --------------------------------------------------------------------
@@ -593,7 +739,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 +757,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 +782,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 +807,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 +823,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 +839,6 @@
 		}
 
 		$sql .= substr($cases, 0, -2);
-
 		$sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
 
 		return $sql;
@@ -739,19 +882,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 +913,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 +942,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/system/libraries/Session.php b/system/libraries/Session.php
index 04103a4..66b39a6 100644
--- a/system/libraries/Session.php
+++ b/system/libraries/Session.php
@@ -25,8 +25,6 @@
  * @filesource
  */
 
-// ------------------------------------------------------------------------
-
 /**
  * Session Class
  *
@@ -67,7 +65,7 @@
 	 */
 	public function __construct($params = array())
 	{
-		log_message('debug', "Session Class Initialized");
+		log_message('debug', 'Session Class Initialized');
 
 		// Set the super object to a local variable for use throughout the class
 		$this->CI =& get_instance();
@@ -93,14 +91,14 @@
 			$this->CI->load->library('encrypt');
 		}
 
-		// Are we using a database?  If so, load it
-		if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
+		// Are we using a database? If so, load it
+		if ($this->sess_use_database === TRUE && $this->sess_table_name != '')
 		{
 			$this->CI->load->database();
 		}
 
-		// Set the "now" time.  Can either be GMT or server time, based on the
-		// config prefs.  We use this to set the "last activity" time
+		// Set the "now" time. Can either be GMT or server time, based on the
+		// config prefs. We use this to set the "last activity" time
 		$this->now = $this->_get_time();
 
 		// Set the session length. If the session expiration is
@@ -114,7 +112,7 @@
 		$this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
 
 		// Run the Session routine. If a session doesn't exist we'll
-		// create a new one.  If it does, we'll update it.
+		// create a new one. If it does, we'll update it.
 		if ( ! $this->sess_read())
 		{
 			$this->sess_create();
@@ -133,7 +131,7 @@
 		// Delete expired sessions if necessary
 		$this->_sess_gc();
 
-		log_message('debug', "Session routines successfully run");
+		log_message('debug', 'Session routines successfully run');
 	}
 
 	// --------------------------------------------------------------------
@@ -166,7 +164,7 @@
 			$hash	 = substr($session, strlen($session)-32); // get last 32 chars
 			$session = substr($session, 0, strlen($session)-32);
 
-			// Does the md5 hash match?  This is to prevent manipulation of session data in userspace
+			// Does the md5 hash match? This is to prevent manipulation of session data in userspace
 			if ($hash !==  md5($session.$this->encryption_key))
 			{
 				log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
@@ -179,7 +177,7 @@
 		$session = $this->_unserialize($session);
 
 		// Is the session data we unserialized an array with the correct format?
-		if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
+		if ( ! is_array($session) OR ! isset($session['session_id'], $session['ip_address'], $session['user_agent'], $session['last_activity']))
 		{
 			$this->sess_destroy();
 			return FALSE;
@@ -192,15 +190,15 @@
 			return FALSE;
 		}
 
-		// Does the IP Match?
-		if ($this->sess_match_ip == TRUE AND $session['ip_address'] !== $this->CI->input->ip_address())
+		// Does the IP match?
+		if ($this->sess_match_ip == TRUE && $session['ip_address'] !== $this->CI->input->ip_address())
 		{
 			$this->sess_destroy();
 			return FALSE;
 		}
 
 		// Does the User Agent Match?
-		if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120)))
+		if ($this->sess_match_useragent == TRUE && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120)))
 		{
 			$this->sess_destroy();
 			return FALSE;
@@ -223,7 +221,7 @@
 
 			$query = $this->CI->db->get($this->sess_table_name);
 
-			// No result?  Kill it!
+			// No result? Kill it!
 			if ($query->num_rows() === 0)
 			{
 				$this->sess_destroy();
@@ -232,7 +230,7 @@
 
 			// Is there custom data?  If so, add it to the main session array
 			$row = $query->row();
-			if (isset($row->user_data) AND $row->user_data != '')
+			if (isset($row->user_data) && $row->user_data != '')
 			{
 				$custom_data = $this->_unserialize($row->user_data);
 
@@ -282,7 +280,7 @@
 			$cookie_userdata[$val] = $this->userdata[$val];
 		}
 
-		// Did we find any custom data?  If not, we turn the empty array into a string
+		// Did we find any custom data? If not, we turn the empty array into a string
 		// since there's no reason to serialize and store an empty array in the DB
 		if (count($custom_userdata) === 0)
 		{
@@ -298,7 +296,7 @@
 		$this->CI->db->where('session_id', $this->userdata['session_id']);
 		$this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
 
-		// Write the cookie.  Notice that we manually pass the cookie data array to the
+		// Write the cookie. Notice that we manually pass the cookie data array to the
 		// _set_cookie() function. Normally that function will store $this->userdata, but
 		// in this case that array contains custom data, which we do not want in the cookie.
 		$this->_set_cookie($cookie_userdata);
@@ -324,13 +322,12 @@
 		$sessid .= $this->CI->input->ip_address();
 
 		$this->userdata = array(
-							'session_id'	=> md5(uniqid($sessid, TRUE)),
-							'ip_address'	=> $this->CI->input->ip_address(),
-							'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
-							'last_activity'	=> $this->now,
-							'user_data'		=> ''
-							);
-
+					'session_id'	=> md5(uniqid($sessid, TRUE)),
+					'ip_address'	=> $this->CI->input->ip_address(),
+					'user_agent'	=> substr($this->CI->input->user_agent(), 0, 120),
+					'last_activity'	=> $this->now,
+					'user_data'	=> ''
+				);
 
 		// Save the data to the DB if needed
 		if ($this->sess_use_database === TRUE)
@@ -357,6 +354,35 @@
 			return;
 		}
 
+		// _set_cookie() will handle this for us if we aren't using database sessions
+		// by pushing all userdata to the cookie.
+		$cookie_data = NULL;
+
+		/* Changing the session ID during an AJAX call causes problems,
+		 * so we'll only update our last_activity
+		 */
+		if ($this->CI->input->is_ajax_request())
+		{
+			$this->userdata['last_activity'] = $this->now;
+
+			// Update the session ID and last_activity field in the DB if needed
+			if ($this->sess_use_database === TRUE)
+			{
+				// set cookie explicitly to only have our session data
+				$cookie_data = array();
+				foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
+				{
+					$cookie_data[$val] = $this->userdata[$val];
+				}
+
+				$this->CI->db->query($this->CI->db->update_string($this->sess_table_name,
+											array('last_activity' => $this->userdata['last_activity']),
+											array('session_id' => $this->userdata['session_id'])));
+			}
+
+			return $this->_set_cookie($cookie_data);
+		}
+
 		// Save the old session id so we know which record to
 		// update in the database if we need it
 		$old_sessid = $this->userdata['session_id'];
@@ -374,10 +400,6 @@
 		$this->userdata['session_id'] = $new_sessid = md5(uniqid($new_sessid, TRUE));
 		$this->userdata['last_activity'] = $this->now;
 
-		// _set_cookie() will handle this for us if we aren't using database sessions
-		// by pushing all userdata to the cookie.
-		$cookie_data = NULL;
-
 		// Update the session ID and last_activity field in the DB if needed
 		if ($this->sess_use_database === TRUE)
 		{
@@ -405,7 +427,7 @@
 	public function sess_destroy()
 	{
 		// Kill the session DB row
-		if ($this->sess_use_database === TRUE AND isset($this->userdata['session_id']))
+		if ($this->sess_use_database === TRUE && isset($this->userdata['session_id']))
 		{
 			$this->CI->db->where('session_id', $this->userdata['session_id']);
 			$this->CI->db->delete($this->sess_table_name);
@@ -413,13 +435,13 @@
 
 		// Kill the cookie
 		setcookie(
-					$this->sess_cookie_name,
-					addslashes(serialize(array())),
-					($this->now - 31500000),
-					$this->cookie_path,
-					$this->cookie_domain,
-					0
-				);
+				$this->sess_cookie_name,
+				addslashes(serialize(array())),
+				($this->now - 31500000),
+				$this->cookie_path,
+				$this->cookie_domain,
+				0
+			);
 	}
 
 	// --------------------------------------------------------------------
@@ -535,7 +557,7 @@
 	 */
 	public function keep_flashdata($key)
 	{
-		// 'old' flashdata gets removed.  Here we mark all
+		// 'old' flashdata gets removed. Here we mark all
 		// flashdata as 'new' to preserve it from _flashdata_sweep()
 		// Note the function will return FALSE if the $key
 		// provided cannot be found
@@ -586,7 +608,6 @@
 	 *
 	 * @return	void
 	 */
-
 	protected function _flashdata_sweep()
 	{
 		$userdata = $this->all_userdata();
@@ -609,13 +630,9 @@
 	 */
 	protected function _get_time()
 	{
-		if (strtolower($this->time_reference) === 'gmt')
-		{
-			$now = time();
-			return mktime(gmdate('H', $now), gmdate('i', $now), gmdate('s', $now), gmdate('m', $now), gmdate('d', $now), gmdate('Y', $now));
-		}
-
-		return time();
+		return (strtolower($this->time_reference) === 'gmt')
+			? mktime(gmdate('H'), gmdate('i'), gmdate('s'), gmdate('m'), gmdate('d'), gmdate('Y'))
+			: time();
 	}
 
 	// --------------------------------------------------------------------
@@ -649,13 +666,13 @@
 
 		// Set the cookie
 		setcookie(
-					$this->sess_cookie_name,
-					$cookie_data,
-					$expire,
-					$this->cookie_path,
-					$this->cookie_domain,
-					$this->cookie_secure
-				);
+				$this->sess_cookie_name,
+				$cookie_data,
+				$expire,
+				$this->cookie_path,
+				$this->cookie_domain,
+				$this->cookie_secure
+			);
 	}
 
 	// --------------------------------------------------------------------
@@ -687,8 +704,11 @@
 	 *
 	 * This function converts any slashes found into a temporary marker
 	 *
+	 * @param	string
+	 * @param	string
+	 * @return	void
 	 */
-	function _escape_slashes(&$val, $key)
+	protected function _escape_slashes(&$val, $key)
 	{
 		if (is_string($val))
 		{
@@ -725,6 +745,9 @@
 	 *
 	 * This function converts any slash markers back into actual slashes
 	 *
+	 * @param	string
+	 * @param	string
+	 * @return	void
 	 */
 	protected function _unescape_slashes(&$val, $key)
 	{
@@ -763,9 +786,7 @@
 		}
 	}
 
-
 }
-// END Session Class
 
 /* End of file Session.php */
 /* Location: ./system/libraries/Session.php */
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 10f5507..649616e 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
 
@@ -100,6 +102,8 @@
 -  Fixed a bug (#561) - Errors in :doc:`XML-RPC Library <libraries/xmlrpc>` were not properly escaped.
 -  Fixed a bug (#904) - ``CI_Loader::initialize()`` caused a PHP Fatal error to be triggered if error level E_STRICT is used.
 -  Fixed a hosting edge case where an empty $_SERVER['HTTPS'] variable would evaluate to 'on'
+-  Fixed a bug (#154) - ``CI_Session::sess_update()`` caused the session to be destroyed on pages where multiple AJAX requests were executed at once.
+-  Fixed a possible bug in ``CI_Input::is_ajax_request()`` where some clients might not send the X-Requested-With HTTP header value exactly as 'XmlHttpRequest'.
 
 Version 2.1.0
 =============
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::