Merge pull request #948 from mptre/develop

Added redis cache driver
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index 39c19cd..65f1f18 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -934,8 +934,8 @@
 	 *
 	 * This function escapes column and table names
 	 *
-	 * @param	string
-	 * @return	string
+	 * @param	mixed
+	 * @return	mixed
 	 */
 	public function escape_identifiers($item)
 	{
@@ -943,25 +943,39 @@
 		{
 			return $item;
 		}
+		elseif (is_array($item))
+		{
+			foreach ($item as $key => $value)
+			{
+				$item[$key] = $this->escape_identifiers($value);
+			}
+
+			return $item;
+		}
+
+		static $preg_ec = array();
+
+		if (empty($preg_ec))
+		{
+			if (is_array($this->_escape_char))
+			{
+				$preg_ec = array(preg_quote($this->_escape_char[0]), preg_quote($this->_escape_char[1]));
+			}
+			else
+			{
+				$preg_ec[0] = $preg_ec[1] = preg_quote($this->_escape_char);
+			}
+		}
 
 		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);
+				return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?\./i', $preg_ec[0].'$1'.$preg_ec[1].'.', $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);
+		return preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?(\.)?/i', $preg_ec[0].'$1'.$preg_ec[1].'$2', $item);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 3ed5562..5d0a2ae 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -547,7 +547,7 @@
 	{
 		if ($key === NULL OR $values === NULL)
 		{
-			return;
+			return $this;
 		}
 
 		$type = $this->_group_get_type($type);
diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php
index 8434cd2..d328aa2 100644
--- a/system/database/drivers/cubrid/cubrid_forge.php
+++ b/system/database/drivers/cubrid/cubrid_forge.php
@@ -158,7 +158,7 @@
 		if (count($primary_keys) > 0)
 		{
 			$key_name = $this->db->escape_identifiers('pk_'.$table.'_'.implode('_', $primary_keys));
-			$sql .= ",\n\tCONSTRAINT ".$key_name.' PRIMARY KEY('.implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tCONSTRAINT ".$key_name.' PRIMARY KEY('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -168,7 +168,7 @@
 				if (is_array($key))
 				{
 					$key_name = $this->db->escape_identifiers('idx_'.$table.implode('_', $key));
-					$key = $this->db->protect_identifiers($key);
+					$key = $this->db->escape_identifiers($key);
 				}
 				else
 				{
diff --git a/system/database/drivers/interbase/interbase_forge.php b/system/database/drivers/interbase/interbase_forge.php
index 4e6a60c..3f9967f 100644
--- a/system/database/drivers/interbase/interbase_forge.php
+++ b/system/database/drivers/interbase/interbase_forge.php
@@ -87,7 +87,7 @@
 	{
 		$sql = 'CREATE TABLE ';
 
-		$sql .= $this->db->protect_identifiers($table).'(';
+		$sql .= $this->db->escape_identifiers($table).'(';
 		$current_field_count = 0;
 
 		foreach ($fields as $field => $attributes)
@@ -135,7 +135,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$primary_keys = $this->db->protect_identifiers($primary_keys);
+			$primary_keys = $this->db->escape_identifiers($primary_keys);
 			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $primary_keys).')';
 		}
 
@@ -144,7 +144,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index 3eaea2e..87094e7 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -43,7 +43,7 @@
 	public $dbdriver = 'mssql';
 
 	// The character used for escaping
-	protected $_escape_char = '';
+	protected $_escape_char = '"';
 
 	// clause and character used for LIKE escape sequences
 	protected $_like_escape_str = " ESCAPE '%s' ";
@@ -57,6 +57,17 @@
 	protected $_count_string = 'SELECT COUNT(*) AS ';
 	protected $_random_keyword = ' NEWID()';
 
+	// MSSQL-specific properties
+	protected $_quoted_identifier = TRUE;
+
+	/*
+	 * Constructor
+	 *
+	 * Appends the port number to the hostname, if needed.
+	 *
+	 * @param	array
+	 * @return	void
+	 */
 	public function __construct($params)
 	{
 		parent::__construct($params);
@@ -67,6 +78,8 @@
 		}
 	}
 
+	// --------------------------------------------------------------------
+
 	/**
 	 * Non-persistent database connection
 	 *
@@ -74,7 +87,7 @@
 	 */
 	public function db_connect()
 	{
-		return @mssql_connect($this->hostname, $this->username, $this->password);
+		return $this->_mssql_connect();
 	}
 
 	// --------------------------------------------------------------------
@@ -86,7 +99,35 @@
 	 */
 	public function db_pconnect()
 	{
-		return @mssql_pconnect($this->hostname, $this->username, $this->password);
+		return $this->_mssql_connect(TRUE);
+	}
+
+	// --------------------------------------------------------------------
+
+	/*
+	 * MSSQL Connect
+	 *
+	 * @param	bool
+	 * @return	resource
+	 */
+	protected function _mssql_connect($persistent = FALSE)
+	{
+		$conn_id = ($persistent)
+				? @mssql_pconnect($this->hostname, $this->username, $this->password)
+				: @mssql_connect($this->hostname, $this->username, $this->password);
+
+		if ( ! $conn_id)
+		{
+			return FALSE;
+		}
+
+		// Determine how identifiers are escaped
+		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+		$query = $query->row_array();
+		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query->qi;
+		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+
+		return $conn_id;
 	}
 
 	// --------------------------------------------------------------------
@@ -106,7 +147,7 @@
 
 		// Note: The brackets are required in the event that the DB name
 		// contains reserved characters
-		if (@mssql_select_db('['.$database.']', $this->conn_id))
+		if (@mssql_select_db($this->escape_identifiers($database), $this->conn_id))
 		{
 			$this->database = $database;
 			return TRUE;
diff --git a/system/database/drivers/mssql/mssql_forge.php b/system/database/drivers/mssql/mssql_forge.php
index 4d98221..3a3528f 100644
--- a/system/database/drivers/mssql/mssql_forge.php
+++ b/system/database/drivers/mssql/mssql_forge.php
@@ -103,7 +103,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -111,7 +111,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
diff --git a/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php
index 7656c28..d22454d 100644
--- a/system/database/drivers/mysql/mysql_forge.php
+++ b/system/database/drivers/mysql/mysql_forge.php
@@ -142,7 +142,7 @@
 		if (count($primary_keys) > 0)
 		{
 			$key_name = $this->db->escape_identifiers(implode('_', $primary_keys));
-			$sql .= ",\n\tPRIMARY KEY ".$key_name.' ('.implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY ".$key_name.' ('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -152,7 +152,7 @@
 				if (is_array($key))
 				{
 					$key_name = $this->db->escape_identifiers(implode('_', $key));
-					$key = $this->db->protect_identifiers($key);
+					$key = $this->db->escape_identifiers($key);
 				}
 				else
 				{
diff --git a/system/database/drivers/mysql/mysql_utility.php b/system/database/drivers/mysql/mysql_utility.php
index 643682f..f0bbc66 100644
--- a/system/database/drivers/mysql/mysql_utility.php
+++ b/system/database/drivers/mysql/mysql_utility.php
@@ -65,7 +65,7 @@
 			}
 
 			// Get the table schema
-			$query = $this->db->query('SHOW CREATE TABLE '.$this->db->protect_identifiers($this->db->database).'.'.$this->db->protect_identifiers($table));
+			$query = $this->db->query('SHOW CREATE TABLE '.$this->db->escape_identifiers($this->db->database.'.'.$table));
 
 			// No result means the table name was invalid
 			if ($query === FALSE)
@@ -120,7 +120,7 @@
 							TRUE);
 
 				// Create a string of field names
-				$field_str .= $this->db->protect_identifiers($field->name).', ';
+				$field_str .= $this->db->escape_identifiers($field->name).', ';
 				$i++;
 			}
 
diff --git a/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php
index e0e98f1..b74c775 100644
--- a/system/database/drivers/mysqli/mysqli_forge.php
+++ b/system/database/drivers/mysqli/mysqli_forge.php
@@ -152,7 +152,7 @@
 				if (is_array($key))
 				{
 					$key_name = $this->db->escape_identifiers(implode('_', $key));
-					$key = $this->db->protect_identifiers($key);
+					$key = $this->db->escape_identifiers($key);
 				}
 				else
 				{
diff --git a/system/database/drivers/oci8/oci8_forge.php b/system/database/drivers/oci8/oci8_forge.php
index be09375..92e8c02 100644
--- a/system/database/drivers/oci8/oci8_forge.php
+++ b/system/database/drivers/oci8/oci8_forge.php
@@ -100,7 +100,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tCONSTRAINT ".$table.' PRIMARY KEY ('.implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tCONSTRAINT ".$table.' PRIMARY KEY ('.implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -108,7 +108,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tUNIQUE COLUMNS (".implode(', ', $key).')';
diff --git a/system/database/drivers/odbc/odbc_forge.php b/system/database/drivers/odbc/odbc_forge.php
index 5c0b200..b074c58 100644
--- a/system/database/drivers/odbc/odbc_forge.php
+++ b/system/database/drivers/odbc/odbc_forge.php
@@ -104,7 +104,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -112,7 +112,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
diff --git a/system/database/drivers/pdo/pdo_forge.php b/system/database/drivers/pdo/pdo_forge.php
index 5645750..02ceb74 100644
--- a/system/database/drivers/pdo/pdo_forge.php
+++ b/system/database/drivers/pdo/pdo_forge.php
@@ -112,7 +112,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -120,7 +120,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
diff --git a/system/database/drivers/postgre/postgre_forge.php b/system/database/drivers/postgre/postgre_forge.php
index fc11245..c434e95 100644
--- a/system/database/drivers/postgre/postgre_forge.php
+++ b/system/database/drivers/postgre/postgre_forge.php
@@ -158,13 +158,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			// Something seems to break when passing an array to escape_identifiers()
-			foreach ($primary_keys as $index => $key)
-			{
-				$primary_keys[$index] = $this->db->escape_identifiers($key);
-			}
-
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $primary_keys).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		$sql .= "\n);";
@@ -174,7 +168,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				foreach ($key as $field)
diff --git a/system/database/drivers/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php
index ba7dc90..71eed7d 100644
--- a/system/database/drivers/sqlite/sqlite_forge.php
+++ b/system/database/drivers/sqlite/sqlite_forge.php
@@ -136,7 +136,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -144,7 +144,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
diff --git a/system/database/drivers/sqlite3/sqlite3_forge.php b/system/database/drivers/sqlite3/sqlite3_forge.php
index 84651ab..f8bd116 100644
--- a/system/database/drivers/sqlite3/sqlite3_forge.php
+++ b/system/database/drivers/sqlite3/sqlite3_forge.php
@@ -143,7 +143,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -151,7 +151,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->db->escape_identifiers($key));
 
 				$sql .= ",\n\tUNIQUE (".implode(', ', $key).')';
diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php
index 74e11c3..655a9e9 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_driver.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php
@@ -43,7 +43,7 @@
 	public $dbdriver = 'sqlsrv';
 
 	// The character used for escaping
-	protected $_escape_char = '';
+	protected $_escape_char = '"';
 
 	// clause and character used for LIKE escape sequences
 	protected $_like_escape_str = " ESCAPE '%s' ";
@@ -57,6 +57,9 @@
 	protected $_count_string = 'SELECT COUNT(*) AS ';
 	protected $_random_keyword = ' NEWID()';
 
+	// SQLSRV-specific properties
+	protected $_quoted_identifier = TRUE;
+
 	/**
 	 * Non-persistent database connection
 	 *
@@ -83,7 +86,15 @@
 			unset($connection['UID'], $connection['PWD']);
 		}
 
-		return sqlsrv_connect($this->hostname, $connection);
+		$conn_id = sqlsrv_connect($this->hostname, $connection);
+
+		// Determine how identifiers are escaped
+		$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
+		$query = $query->row_array();
+		$this->_quoted_identifier = empty($query) ? FALSE : (bool) $query->qi;
+		$this->_escape_char = ($this->_quoted_identifier) ? '"' : array('[', ']');
+
+		return $conn_id;
 	}
 
 	// --------------------------------------------------------------------
@@ -451,7 +462,10 @@
 	 */
 	protected function _limit($sql, $limit, $offset)
 	{
-		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.($limit + $offset).' ', $sql);
+		// As of SQL Server 2012 (11.0.*) OFFSET is supported
+		return version_compare($this->version(), '11', '>=')
+			? $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY'
+			: preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.($limit + $offset).' ', $sql);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/sqlsrv/sqlsrv_forge.php b/system/database/drivers/sqlsrv/sqlsrv_forge.php
index 559746e..e6f7e1a 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_forge.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_forge.php
@@ -103,7 +103,7 @@
 
 		if (count($primary_keys) > 0)
 		{
-			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->protect_identifiers($primary_keys)).')';
+			$sql .= ",\n\tPRIMARY KEY (".implode(', ', $this->db->escape_identifiers($primary_keys)).')';
 		}
 
 		if (is_array($keys) && count($keys) > 0)
@@ -111,7 +111,7 @@
 			foreach ($keys as $key)
 			{
 				$key = is_array($key)
-					? $this->db->protect_identifiers($key)
+					? $this->db->escape_identifiers($key)
 					: array($this->escape_identifiers($key));
 
 				$sql .= ",\n\tFOREIGN KEY (".implode(', ', $key).')';
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
index 959e2ee..ce5e030 100644
--- a/system/libraries/Encrypt.php
+++ b/system/libraries/Encrypt.php
@@ -104,8 +104,7 @@
 				return $this->encryption_key;
 			}
 
-			$CI =& get_instance();
-			$key = $CI->config->item('encryption_key');
+			$key = config_item('encryption_key');
 
 			if ($key === FALSE)
 			{
diff --git a/tests/codeigniter/libraries/Encrypt_test.php b/tests/codeigniter/libraries/Encrypt_test.php
new file mode 100644
index 0000000..0669901
--- /dev/null
+++ b/tests/codeigniter/libraries/Encrypt_test.php
@@ -0,0 +1,71 @@
+<?php
+
+class Encrypt_test extends CI_TestCase {
+
+  public function set_up()
+  {
+    $obj = new StdClass;
+    $obj->encrypt = new Mock_Libraries_Encrypt();
+    
+    $this->ci_instance($obj);
+    $this->encrypt = $obj->encrypt;
+
+    $this->ci_set_config('encryption_key', "Encryptin'glike@boss!");
+    $this->msg = 'My secret message';
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_encode()
+  {
+    $this->assertNotEquals($this->msg, $this->encrypt->encode($this->msg));
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_decode()
+  {
+    $encoded_msg = $this->encrypt->encode($this->msg);
+    $this->assertEquals($this->msg, $this->encrypt->decode($encoded_msg));
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_optional_key()
+  {
+    $key = 'Ohai!ù0129°03182%HD1892P0';
+    $encoded_msg = $this->encrypt->encode($this->msg, $key);
+    $this->assertEquals($this->msg, $this->encrypt->decode($encoded_msg, $key));
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_default_cipher()
+  {
+    $this->assertEquals('rijndael-256', $this->encrypt->get_cipher());
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_set_cipher()
+  {
+    $this->encrypt->set_cipher(MCRYPT_BLOWFISH);
+    $this->assertEquals('blowfish', $this->encrypt->get_cipher());
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_default_mode()
+  {
+    $this->assertEquals('cbc', $this->encrypt->get_mode());
+  }
+
+  // --------------------------------------------------------------------
+
+  public function test_set_mode()
+  {
+    $this->encrypt->set_mode(MCRYPT_MODE_CFB);
+    $this->assertEquals('cfb', $this->encrypt->get_mode());
+  }
+
+}
\ No newline at end of file
diff --git a/tests/mocks/libraries/encrypt.php b/tests/mocks/libraries/encrypt.php
new file mode 100644
index 0000000..a9bbaaf
--- /dev/null
+++ b/tests/mocks/libraries/encrypt.php
@@ -0,0 +1,15 @@
+<?php
+
+class Mock_Libraries_Encrypt extends CI_Encrypt {
+
+  // Overide inaccesible protected method
+  public function __call($method, $params)
+  {
+    if (is_callable(array($this, '_'.$method)))
+    {
+      return call_user_func_array(array($this, '_'.$method), $params);
+    }
+
+    throw new BadMethodCallException('Method '.$method.' was not found');
+  }
+}
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 256de95..25b42b2 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -88,10 +88,17 @@
    -  Removed protect_identifiers() and renamed internal method _protect_identifiers() to it instead - it was just an alias.
    -  MySQL and MySQLi drivers now require at least MySQL version 5.1.
    -  db_set_charset() now only requires one parameter (collation was only needed due to legacy support for MySQL versions prior to 5.1).
-   -  Added DSN string support for CUBRID.
-   -  Added persistent connections support for CUBRID.
-   -  Added random ordering support for MSSQL, SQLSRV.
    -  Added support for SQLite3 database driver.
+   -  Improved support of the CUBRID driver, including:
+	 - Added DSN string support.
+	 - Added persistent connections support.
+	 - Improved list_databases() in :doc:`Database Utility <database/utilities>` (until now only the currently used database was returned).
+   -  Improved support of the MSSQL and SQLSRV drivers, including:
+	 - Added random ordering support.
+	 - Added support for optimize_table() in :doc:`Database Utility <database/utilities>`.
+	 - Added escaping with QUOTE_IDENTIFIER setting detection.
+	 - Added port handling support for UNIX-based systems (MSSQL driver).
+	 - Added OFFSET support for SQL Server 2012 and above.
    -  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>`.
@@ -101,13 +108,11 @@
 	 -  num_rows() is now only called explicitly by the developer and no longer re-executes statements.
    -  Added replace() support for SQLite.
    -  Renamed internal method _escape_identifiers() to escape_identifiers().
+   -  Updated escape_identifiers() to accept an array of fields as well as strings.
    -  Added SQLite support for drop_table() in :doc:`Database Forge <database/forge>`.
    -  Added ODBC support for create_database(), drop_database() and drop_table() in :doc:`Database Forge <database/forge>`.
    -  Added PDO support for create_database(), drop_database and drop_table() in :doc:`Database Forge <database/forge>`.
-   -  Added MSSQL, SQLSRV support for optimize_table() in :doc:`Database Utility <database/utilities>`.
-   -  Improved CUBRID support for list_databases() in :doc:`Database Utility <database/utilities>` (until now only the currently used database was returned).
    -  Added unbuffered_row() method for getting a row without prefetching whole result (consume less memory).
-   -  Added port handling support for MSSQL on UNIX-based systems.
 
 -  Libraries
 
@@ -238,6 +243,7 @@
 -  Fixed a bug where the magic_quotes_runtime setting wasn't turned off for PHP 5.3 (where it is indeed deprecated, but not non-existent).
 -  Fixed a bug (#666) - :doc:`Output library <libraries/output>`'s set_content_type() method didn't set the document charset.
 -  Fixed a bug (#784, #861) - :doc:`Database Forge <database/forge>` method ``create_table()`` used to accept constraints for MSSQL/SQLSRV integer-type columns.
+-  Fixed a bug (#706) - SQLSRV/MSSSQL didn't escape field names.
 
 Version 2.1.1
 =============