Merge changes from develop
diff --git a/.travis.yml b/.travis.yml
index 62acf05..1c7e876 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -29,4 +29,4 @@
 branches:
   only:
     - develop
-    - /^feature\/.+$/
\ No newline at end of file
+    - /^feature.+/
diff --git a/system/database/DB_driver.php b/system/database/DB_driver.php
index ea2a53e..ea56d38 100644
--- a/system/database/DB_driver.php
+++ b/system/database/DB_driver.php
@@ -1124,30 +1124,19 @@
 	 *
 	 * @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
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
 		foreach ($values as $key => $val)
 		{
 			$valstr[] = $key.' = '.$val;
 		}
 
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
 		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
-			.$where
-			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '')
-			.($limit ? ' LIMIT '.$limit : '');
+			.$this->_compile_wh('qb_where')
+			.$this->_compile_order_by()
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
 	}
 
 	// --------------------------------------------------------------------
@@ -1160,7 +1149,7 @@
 	 */
 	protected function _has_operator($str)
 	{
-		return (bool) preg_match('/(\s|<|>|!|=|IS NULL|IS NOT NULL|BETWEEN)/i', trim($str));
+		return (bool) preg_match('/(<|>|!|=|\sIS NULL|\sIS NOT NULL|\sBETWEEN|\sLIKE|\sIN\s*\(|\s)/i', trim($str));
 	}
 
 	// --------------------------------------------------------------------
@@ -1173,8 +1162,31 @@
 	 */
 	protected function _get_operator($str)
 	{
-		return preg_match('/(=|!|<|>| IS NULL| IS NOT NULL| BETWEEN)/i', $str, $match)
-			? $match[1] : FALSE;
+		static $_operators;
+
+		if (empty($_operators))
+		{
+			$_les = ($this->_like_escape_str !== '')
+				? preg_quote(trim(sprintf($this->_like_escape_str, $this->_like_escape_chr)))
+				: '';
+
+			$_operators = array(
+				'\s*(?:<|>|!)?=\s*',		// =, <=, >=, !=
+				'\s*<>?\s*',			// <, <>
+				'\s*>\s*',			// >
+				'\s+IS NULL',			// IS NULL
+				'\s+IS NOT NULL',		// IS NOT NULL
+				'\s+BETWEEN\s+\S+\s+AND\s+\S+',	// BETWEEN value AND value
+				'\s+IN\s*\([^\)]+\)',		// IN(list)
+				'\s+NOT IN\s*\([^\)]+\)',	// NOT IN (list)
+				'\s+LIKE\s+\S+'.$_les,		// LIKE 'expr'[ ESCAPE '%s']
+				'\s+NOT LIKE\s+\S+'.$_les	// NOT LIKE 'expr'[ ESCAPE '%s']
+			);
+
+		}
+
+		return preg_match('/'.implode('|', $_operators).'/i', $str, $match)
+			? $match[0] : FALSE;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/DB_query_builder.php b/system/database/DB_query_builder.php
index 54510ec..139f467 100644
--- a/system/database/DB_query_builder.php
+++ b/system/database/DB_query_builder.php
@@ -47,7 +47,6 @@
 	protected $qb_from			= array();
 	protected $qb_join			= array();
 	protected $qb_where			= array();
-	protected $qb_like			= array();
 	protected $qb_groupby			= array();
 	protected $qb_having			= array();
 	protected $qb_keys			= array();
@@ -55,7 +54,6 @@
 	protected $qb_offset			= FALSE;
 	protected $qb_orderby			= array();
 	protected $qb_set			= array();
-	protected $qb_wherein			= array();
 	protected $qb_aliased_tables		= array();
 	protected $qb_store_array		= array();
 	protected $qb_where_group_started	= FALSE;
@@ -417,7 +415,7 @@
 	 */
 	public function where($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_where($key, $value, 'AND ', $escape);
+		return $this->_wh('qb_where', $key, $value, 'AND ', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -435,24 +433,27 @@
 	 */
 	public function or_where($key, $value = NULL, $escape = NULL)
 	{
-		return $this->_where($key, $value, 'OR ', $escape);
+		return $this->_wh('qb_where', $key, $value, 'OR ', $escape);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where
+	 * WHERE, HAVING
 	 *
-	 * Called by where() or or_where()
+	 * Called by where(), or_where(), having(), or_having()
 	 *
+	 * @param	string	'qb_where' or 'qb_having'
 	 * @param	mixed
 	 * @param	mixed
 	 * @param	string
-	 * @param	mixed
+	 * @param	bool
 	 * @return	object
 	 */
-	protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
+	protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL)
 	{
+		$qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where';
+
 		if ( ! is_array($key))
 		{
 			$key = array($key => $value);
@@ -463,17 +464,10 @@
 
 		foreach ($key as $k => $v)
 		{
-			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			$prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0)
 				? $this->_group_get_type('')
 				: $this->_group_get_type($type);
 
-			if ($escape === TRUE)
-			{
-				$k = (($op = $this->_get_operator($k)) !== FALSE)
-					? $this->escape_identifiers(trim(substr($k, 0, strpos($k, $op)))).' '.strstr($k, $op)
-					: $this->escape_identifiers(trim($k));
-			}
-
 			if (is_null($v) && ! $this->_has_operator($k))
 			{
 				// value appears not to have been set, assign the test to IS NULL
@@ -484,7 +478,7 @@
 			{
 				if ($escape === TRUE)
 				{
-					$v = ' '.$this->escape($v);
+					$v = ' '.(is_int($v) ? $v : $this->escape($v));
 				}
 
 				if ( ! $this->_has_operator($k))
@@ -493,11 +487,11 @@
 				}
 			}
 
-			$this->qb_where[] = $prefix.$k.$v;
+			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_where[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'where';
+				$this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+				$this->qb_cache_exists[] = substr($qb_key, 3);
 			}
 
 		}
@@ -602,27 +596,25 @@
 
 		$not = ($not) ? ' NOT' : '';
 
+		$where_in = array();
 		foreach ($values as $value)
 		{
-			$this->qb_wherein[] = $this->escape($value);
-		}
-
-		if ($escape === TRUE)
-		{
-			$key = $this->escape_identifiers(trim($key));
+			$wherein[] = $this->escape($value);
 		}
 
 		$prefix = (count($this->qb_where) === 0) ? $this->_group_get_type('') : $this->_group_get_type($type);
-		$this->qb_where[] = $where_in = $prefix.$key.$not.' IN ('.implode(', ', $this->qb_wherein).') ';
+		$where_in = array(
+			'condition' => $prefix.$key.$not.' IN('.implode(', ', $where_in).')',
+			'escape' => $escape
+		);
 
+		$this->qb_where[] = $where_in;
 		if ($this->qb_caching === TRUE)
 		{
 			$this->qb_cache_where[] = $where_in;
 			$this->qb_cache_exists[] = 'where';
 		}
 
-		// reset the array for multiple calls
-		$this->qb_wherein = array();
 		return $this;
 	}
 
@@ -635,12 +627,14 @@
 	 * multiple calls with AND
 	 *
 	 * @param	mixed
-	 * @param	mixed
+	 * @param	string
+	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	public function like($field, $match = '', $side = 'both')
+	public function like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'AND ', $side);
+		return $this->_like($field, $match, 'AND ', $side, '', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -652,12 +646,14 @@
 	 * multiple calls with AND
 	 *
 	 * @param	mixed
-	 * @param	mixed
+	 * @param	string
+	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	public function not_like($field, $match = '', $side = 'both')
+	public function not_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'AND ', $side, 'NOT');
+		return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -669,12 +665,14 @@
 	 * multiple calls with OR
 	 *
 	 * @param	mixed
-	 * @param	mixed
+	 * @param	string
+	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	public function or_like($field, $match = '', $side = 'both')
+	public function or_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'OR ', $side);
+		return $this->_like($field, $match, 'OR ', $side, '', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -686,12 +684,14 @@
 	 * multiple calls with OR
 	 *
 	 * @param	mixed
-	 * @param	mixed
+	 * @param	string
+	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	public function or_not_like($field, $match = '', $side = 'both')
+	public function or_not_like($field, $match = '', $side = 'both', $escape = NULL)
 	{
-		return $this->_like($field, $match, 'OR ', $side, 'NOT');
+		return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -699,56 +699,60 @@
 	/**
 	 * Like
 	 *
-	 * Called by like() or orlike()
+	 * Called by like(), or_like(), not_like, or_not_like()
 	 *
 	 * @param	mixed
-	 * @param	mixed
 	 * @param	string
+	 * @param	string
+	 * @param	string
+	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '')
+	protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = NULL)
 	{
 		if ( ! is_array($field))
 		{
 			$field = array($field => $match);
 		}
 
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+		$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			? $this->_group_get_type('') : $this->_group_get_type($type);
+
 		foreach ($field as $k => $v)
 		{
-			$k = $this->protect_identifiers($k);
-			$prefix = (count($this->qb_like) === 0) ? $this->_group_get_type('') : $this->_group_get_type($type);
 			$v = $this->escape_like_str($v);
 
 			if ($side === 'none')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '{$v}'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}'";
 			}
 			elseif ($side === 'before')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '%{$v}'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}'";
 			}
 			elseif ($side === 'after')
 			{
-				$like_statement = "{$prefix} $k $not LIKE '{$v}%'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '{$v}%'";
 			}
 			else
 			{
-				$like_statement = "{$prefix} $k $not LIKE '%{$v}%'";
+				$like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}%'";
 			}
 
 			// some platforms require an escape sequence definition for LIKE wildcards
 			if ($this->_like_escape_str !== '')
 			{
-				$like_statement = $like_statement.sprintf($this->_like_escape_str, $this->_like_escape_chr);
+				$like_statement .= sprintf($this->_like_escape_str, $this->_like_escape_chr);
 			}
 
-			$this->qb_like[] = $like_statement;
+			$this->qb_where[] = array('condition' => $like_statement, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_like[] = $like_statement;
-				$this->qb_cache_exists[] = 'like';
+				$this->qb_cache_where[] = $like_statement;
+				$this->qb_cache_exists[] = 'where';
 			}
-
 		}
 
 		return $this;
@@ -769,11 +773,15 @@
 
 		$this->qb_where_group_started = TRUE;
 		$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) ? '' : $type;
-		$this->qb_where[] = $value = $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (';
+		$where = array(
+			'condition' => $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (',
+			'escape' => FALSE
+		);
 
+		$this->qb_where[] = $where;
 		if ($this->qb_caching)
 		{
-			$this->qb_cache_where[] = $value;
+			$this->qb_cache_where[] = $where;
 		}
 
 		return $this;
@@ -825,11 +833,15 @@
 	public function group_end()
 	{
 		$this->qb_where_group_started = FALSE;
-		$this->qb_where[] = $value = str_repeat(' ', $this->qb_where_group_count--) . ')';
+		$where = array(
+			'condition' => str_repeat(' ', $this->qb_where_group_count--).')',
+			'escape' => FALSE
+		);
 
+		$this->qb_where[] = $where;
 		if ($this->qb_caching)
 		{
-			$this->qb_cache_where[] = $value;
+			$this->qb_cache_where[] = $where;
 		}
 
 		return $this;
@@ -862,13 +874,18 @@
 	 * GROUP BY
 	 *
 	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	public function group_by($by)
+	public function group_by($by, $escape = NULL)
 	{
+		is_bool($escape) OR $escape = $this->_protect_identifiers;
+
 		if (is_string($by))
 		{
-			$by = explode(',', $by);
+			$by = ($escape === TRUE)
+				? explode(',', $by)
+				: array($by);
 		}
 
 		foreach ($by as $val)
@@ -877,8 +894,9 @@
 
 			if ($val !== '')
 			{
-				$this->qb_groupby[] = $val = $this->protect_identifiers($val);
+				$val = array('field' => $val, 'escape' => $escape);
 
+				$this->qb_groupby[] = $val;
 				if ($this->qb_caching === TRUE)
 				{
 					$this->qb_cache_groupby[] = $val;
@@ -904,7 +922,7 @@
 	 */
 	public function having($key, $value = '', $escape = NULL)
 	{
-		return $this->_having($key, $value, 'AND ', $escape);
+		return $this->_wh('qb_having', $key, $value, 'AND ', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -921,58 +939,7 @@
 	 */
 	public function or_having($key, $value = '', $escape = NULL)
 	{
-		return $this->_having($key, $value, 'OR ', $escape);
-	}
-
-	// --------------------------------------------------------------------
-
-	/**
-	 * Sets the HAVING values
-	 *
-	 * Called by having() or or_having()
-	 *
-	 * @param	string
-	 * @param	string
-	 * @param	string
-	 * @param	bool
-	 * @return	object
-	 */
-	protected function _having($key, $value = '', $type = 'AND ', $escape = NULL)
-	{
-		if ( ! is_array($key))
-		{
-			$key = array($key => $value);
-		}
-
-		is_bool($escape) OR $escape = $this->_protect_identifiers;
-
-		foreach ($key as $k => $v)
-		{
-			$prefix = (count($this->qb_having) === 0) ? '' : $type;
-
-			$k = $this->_has_operator($k)
-				? $this->protect_identifiers(substr($k, 0, strpos(rtrim($k), ' ')), FALSE, $escape).strchr(rtrim($k), ' ')
-				: $this->protect_identifiers($k, FALSE, $escape);
-
-			if ( ! $this->_has_operator($k))
-			{
-				$k .= ' = ';
-			}
-
-			if ($v !== '')
-			{
-				$v = ' '.$this->escape($v);
-			}
-
-			$this->qb_having[] = $prefix.$k.$v;
-			if ($this->qb_caching === TRUE)
-			{
-				$this->qb_cache_having[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'having';
-			}
-		}
-
-		return $this;
+		return $this->_wh('qb_having', $key, $value, 'OR ', $escape);
 	}
 
 	// --------------------------------------------------------------------
@@ -981,54 +948,50 @@
 	 * Sets the ORDER BY value
 	 *
 	 * @param	string
-	 * @param	string	direction: asc or desc
+	 * @param	string	direction: ASC or DESC
 	 * @param	bool	enable field name escaping
 	 * @return	object
 	 */
 	public function order_by($orderby, $direction = '', $escape = NULL)
 	{
-		if (strtolower($direction) === 'random')
+		$direction = trim($direction);
+
+		if (strtolower($direction) === 'random' OR $orderby === $this->_random_keyword)
 		{
-			$orderby = ''; // Random results want or don't need a field name
-			$direction = $this->_random_keyword;
+			// Random ordered results don't need a field name
+			$orderby = $this->_random_keyword;
+			$direction = '';
 		}
-		elseif (trim($direction) !== '')
+		elseif (empty($orderby))
 		{
-			$direction = in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE) ? ' '.$direction : ' ASC';
+			return $this;
+		}
+		elseif ($direction !== '')
+		{
+			$direction = in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE) ? ' '.$direction : '';
 		}
 
 		is_bool($escape) OR $escape = $this->_protect_identifiers;
 
-		if ($escape === TRUE && strpos($orderby, ',') !== FALSE)
+		if ($escape === FALSE)
 		{
-			$temp = array();
-			foreach (explode(',', $orderby) as $part)
+			$qb_orderby[] = array(array('field' => $orderby, 'direction' => $direction, $escape => FALSE));
+		}
+		else
+		{
+			$qb_orderby = array();
+			foreach (explode(',', $orderby) as $field)
 			{
-				$part = trim($part);
-				if ( ! in_array($part, $this->qb_aliased_tables))
-				{
-					$part = preg_match('/^(.+)\s+(ASC|DESC)$/i', $part, $matches)
-						? $this->protect_identifiers(rtrim($matches[1])).' '.$matches[2]
-						: $this->protect_identifiers($part);
-				}
-
-				$temp[] = $part;
+				$qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE))
+					? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE)
+					: array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE);
 			}
-
-			$orderby = implode(', ', $temp);
-		}
-		elseif ($direction !== $this->_random_keyword && $escape === TRUE)
-		{
-			$orderby = preg_match('/^(.+)\s+(ASC|DESC)$/i', $orderby, $matches)
-				? $this->protect_identifiers(rtrim($matches[1])).' '.$matches[2]
-				: $this->protect_identifiers($orderby);
 		}
 
-		$this->qb_orderby[] = $orderby_statement = $orderby.$direction;
-
+		$this->qb_orderby = array_merge($this->qb_orderby, $qb_orderby);
 		if ($this->qb_caching === TRUE)
 		{
-			$this->qb_cache_orderby[] = $orderby_statement;
+			$this->qb_cache_orderby = array_merge($this->qb_cache_orderby, $qb_orderby);
 			$this->qb_cache_exists[] = 'orderby';
 		}
 
@@ -1044,7 +1007,7 @@
 	 * @param	int	the offset value
 	 * @return	object
 	 */
-	public function limit($value, $offset = NULL)
+	public function limit($value, $offset = FALSE)
 	{
 		is_null($value) OR $this->qb_limit = (int) $value;
 		empty($offset) OR $this->qb_offset = (int) $offset;
@@ -1074,13 +1037,11 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.($offset ? $offset.', ' : '').$limit;
+		return $sql.' LIMIT '.($this->qb_offset ? $this->qb_offset.', ' : '').$this->qb_limit;
 	}
 
 	// --------------------------------------------------------------------
@@ -1557,7 +1518,7 @@
 			return FALSE;
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set, $this->qb_where, $this->qb_orderby, $this->qb_limit);
+		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
 
 		if ($reset === TRUE)
 		{
@@ -1604,7 +1565,7 @@
 			$this->limit($limit);
 		}
 
-		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set, $this->qb_where, $this->qb_orderby, $this->qb_limit, $this->qb_like);
+		$sql = $this->_update($this->protect_identifiers($this->qb_from[0], TRUE, NULL, FALSE), $this->qb_set);
 
 		$this->_reset_write();
 		return $this->query($sql);
@@ -1686,7 +1647,7 @@
 		// Batch this baby
 		for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
 		{
-			$this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, 100), $this->protect_identifiers($index), $this->qb_where));
+			$this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set, $i, 100), $this->protect_identifiers($index)));
 		}
 
 		$this->_reset_write();
@@ -1892,12 +1853,12 @@
 			$this->limit($limit);
 		}
 
-		if (count($this->qb_where) === 0 && count($this->qb_wherein) === 0 && count($this->qb_like) === 0)
+		if (count($this->qb_where) === 0)
 		{
 			return ($this->db_debug) ? $this->display_error('db_del_must_use_where') : FALSE;
 		}
 
-		$sql = $this->_delete($table, $this->qb_where, $this->qb_like, $this->qb_limit);
+		$sql = $this->_delete($table);
 		if ($reset_data)
 		{
 			$this->_reset_write();
@@ -1914,21 +1875,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table
-			.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '')
-			.($limit ? ' LIMIT '.(int) $limit : '');
+		return 'DELETE FROM '.$table.$this->_compile_wh('qb_where')
+			.($this->qb_limit ? ' LIMIT '.$this->qb_limit : '');
 	}
 
 	// --------------------------------------------------------------------
@@ -2066,47 +2018,15 @@
 			$sql .= "\n".implode("\n", $this->qb_join);
 		}
 
-		// Write the "WHERE" portion of the query
-		if (count($this->qb_where) > 0 OR count($this->qb_like) > 0)
+		$sql .= $this->_compile_wh('qb_where')
+			.$this->_compile_group_by()
+			.$this->_compile_wh('qb_having')
+			.$this->_compile_order_by(); // ORDER BY
+
+		// LIMIT
+		if ($this->qb_limit)
 		{
-			$sql .= "\nWHERE ";
-		}
-
-		$sql .= implode("\n", $this->qb_where);
-
-		// Write the "LIKE" portion of the query
-		if (count($this->qb_like) > 0)
-		{
-			if (count($this->qb_where) > 0)
-			{
-				$sql .= "\nAND ";
-			}
-
-			$sql .= implode("\n", $this->qb_like);
-		}
-
-		// Write the "GROUP BY" portion of the query
-		if (count($this->qb_groupby) > 0)
-		{
-			$sql .= "\nGROUP BY ".implode(', ', $this->qb_groupby);
-		}
-
-		// Write the "HAVING" portion of the query
-		if (count($this->qb_having) > 0)
-		{
-			$sql .= "\nHAVING ".implode("\n", $this->qb_having);
-		}
-
-		// Write the "ORDER BY" portion of the query
-		if (count($this->qb_orderby) > 0)
-		{
-			$sql .= "\nORDER BY ".implode(', ', $this->qb_orderby);
-		}
-
-		// Write the "LIMIT" portion of the query
-		if (is_numeric($this->qb_limit))
-		{
-			return $this->_limit($sql."\n", $this->qb_limit, $this->qb_offset);
+			return $this->_limit($sql."\n");
 		}
 
 		return $sql;
@@ -2115,6 +2035,128 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Compile WHERE, HAVING statements
+	 *
+	 * Escapes identifiers in WHERE and HAVING statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * where(), or_where(), having(), or_having are called prior to from(),
+	 * join() and dbprefix is added only if needed.
+	 *
+	 * @param	string	'qb_where' or 'qb_having'
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_wh($qb_key)
+	{
+		if (count($this->$qb_key) > 0)
+		{
+			$sql = ($qb_key === 'qb_having') ? "\nHAVING " : "\nWHERE ";
+
+			for ($i = 0, $c = count($this->$qb_key); $i < $c; $i++)
+			{
+				if ($this->{$qb_key}[$i]['escape'] === FALSE)
+				{
+					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
+					continue;
+				}
+
+				$op = preg_quote($this->_get_operator($this->{$qb_key}[$i]['condition']));
+				if ( ! preg_match('/^(\s*(?:AND|OR)\s+)?(\(?)(.*)('.$op.')(.*(?<!\)))?(\)?)$/i', $this->{$qb_key}[$i]['condition'], $matches))
+				{
+					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
+					continue;
+				}
+
+				// $matches = array(
+				//	0 => 'OR (test <= foo)',	/* the whole thing */
+				//	1 => 'OR ',			/* optional */
+				//	2 => '(',			/* optional */
+				//	3 => 'test',			/* the field name */
+				//	4 => ' <= ',			/* $op */
+				//	5 => 'foo',			/* optional, if $op is e.g. 'IS NULL' */
+				//	6 => ')'			/* optional */
+				// );
+				empty($matches[5]) OR $matches[5] = ' '.$this->protect_identifiers(trim($matches[5]));
+				$this->{$qb_key}[$i] = $matches[1].$matches[2].$this->protect_identifiers(trim($matches[3]))
+						.' '.trim($matches[4]).$matches[5].$matches[6];
+			}
+
+			return implode("\n", $this->$qb_key);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile GROUP BY
+	 *
+	 * Escapes identifiers in GROUP BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * group_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_group_by()
+	{
+		if (count($this->qb_groupby) > 0)
+		{
+			$sql = "\nGROUP BY ";
+
+			for ($i = 0, $c = count($this->qb_groupby); $i < $c; $i++)
+			{
+				$this->qb_groupby[$i] = ($this->qb_groupby[$i]['escape'] === FALSE)
+					? $this->qb_groupby[$i]['field']
+					: $this->protect_identifiers($qb_groupby[$i]['field']);
+			}
+
+			$sql .= implode(', ', $this->qb_groupby);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * Compile ORDER BY
+	 *
+	 * Escapes identifiers in ORDER BY statements at execution time.
+	 *
+	 * Required so that aliases are tracked properly, regardless of wether
+	 * order_by() is called prior to from(), join() and dbprefix is added
+	 * only if needed.
+	 *
+	 * @return	string	SQL statement
+	 */
+	protected function _compile_order_by()
+	{
+		if (count($this->qb_orderby) > 0)
+		{
+			$sql = "\nORDER BY ";
+
+			for ($i = 0, $c = count($this->qb_orderby); $i < $c; $i++)
+			{
+				if ($this->qb_orderby[$i]['escape'] !== FALSE)
+				{
+					$this->qb_orderby[$i]['field'] = $this->protect_identifiers($field);
+				}
+
+				$this->qb_orderby[$i] = $this->qb_orderby[$i]['field'].$this->qb_orderby[$i]['direction'];
+			}
+
+			$sql .= implode(', ', $this->qb_orderby);
+		}
+
+		return '';
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Object to Array
 	 *
 	 * Takes an object as input and converts the class variables to array key/vals
@@ -2321,11 +2363,9 @@
 					'qb_from'		=> array(),
 					'qb_join'		=> array(),
 					'qb_where'		=> array(),
-					'qb_like'		=> array(),
 					'qb_groupby'		=> array(),
 					'qb_having'		=> array(),
 					'qb_orderby'		=> array(),
-					'qb_wherein'		=> array(),
 					'qb_aliased_tables'	=> array(),
 					'qb_no_escape'		=> array(),
 					'qb_distinct'		=> FALSE,
@@ -2350,7 +2390,6 @@
 			'qb_set'	=> array(),
 			'qb_from'	=> array(),
 			'qb_where'	=> array(),
-			'qb_like'	=> array(),
 			'qb_orderby'	=> array(),
 			'qb_keys'	=> array(),
 			'qb_limit'	=> FALSE
diff --git a/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php
index 01d0ee3..7f8f297 100644
--- a/system/database/drivers/cubrid/cubrid_driver.php
+++ b/system/database/drivers/cubrid/cubrid_driver.php
@@ -394,10 +394,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -421,9 +421,9 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN ('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/ibase/ibase_driver.php b/system/database/drivers/ibase/ibase_driver.php
index ab1d1b8..96d6f65 100644
--- a/system/database/drivers/ibase/ibase_driver.php
+++ b/system/database/drivers/ibase/ibase_driver.php
@@ -281,7 +281,10 @@
 	 */
 	protected function _field_data($table)
 	{
-		return $this->_limit('SELECT * FROM '.$this->protect_identifiers($table), 1, NULL);
+		$this->qb_limit = 1;
+		$sql = $this->_limit('SELECT * FROM '.$this->protect_identifiers($table));
+		$this->qb_limit = 0;
+		return $sql;
 	}
 
 	// --------------------------------------------------------------------
@@ -308,29 +311,12 @@
 	 *
 	 * @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 (ignored)
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
-			.$where
-			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '');
+		$this->qb_limit = FALSE;
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -359,19 +345,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -382,22 +361,20 @@
 	 * 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)
+	protected function _limit($sql)
 	{
 		// Limit clause depends on if Interbase or Firebird
 		if (stripos($this->version(), 'firebird') !== FALSE)
 		{
-			$select = 'FIRST '. (int) $limit
-				.($offset ? ' SKIP '. (int) $offset : '');
+			$select = 'FIRST '.$this->qb_limit
+				.($this->qb_offset ? ' SKIP '.$this->qb_offset : '');
 		}
 		else
 		{
 			$select = 'ROWS '
-				.($offset ? (int) $offset.' TO '.($limit + $offset) : (int) $limit);
+				.($this->qb_offset ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
 		}
 
 		return preg_replace('`SELECT`i', 'SELECT '.$select, $sql);
diff --git a/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php
index a5ace02..4369bbe 100644
--- a/system/database/drivers/mssql/mssql_driver.php
+++ b/system/database/drivers/mssql/mssql_driver.php
@@ -368,27 +368,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).' WHERE '.$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -417,23 +403,16 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -444,33 +423,25 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		// As of SQL Server 2012 (11.0.*) OFFSET is supported
-		if (version_compare($this->version(), '11', '>='))
-		{
-			return $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY';
-		}
-
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
 		// however an ORDER BY clause is required for it to work
-		if (version_compare($this->version(), '9', '>=') && $offset && ! empty($this->qb_orderby))
+		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
 			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
 			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
 				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
diff --git a/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php
index 7262591..60bcf3c 100644
--- a/system/database/drivers/mysql/mysql_driver.php
+++ b/system/database/drivers/mysql/mysql_driver.php
@@ -430,10 +430,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -457,9 +457,9 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index b5a1e26..934694c 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -438,10 +438,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -465,11 +465,9 @@
 				.'ELSE '.$k.' END, ';
 		}
 
-		$where = ($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php
index 72cbce5..8e4f4ef 100644
--- a/system/database/drivers/oci8/oci8_driver.php
+++ b/system/database/drivers/oci8/oci8_driver.php
@@ -591,20 +591,17 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			$this->where('rownum <= ',$this->qb_limit, FALSE);
+			$this->qb_limit = FALSE;
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-		empty($limit) OR $conditions[] = 'rownum <= '.$limit;
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -615,15 +612,13 @@
 	 * 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)
+	protected function _limit($sql)
 	{
 		$this->limit_used = TRUE;
-		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($offset + $limit + 1).')'
-			.($offset ? ' WHERE rnum >= '.($offset + 1): '');
+		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
+			.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1): '');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php
index c1f6ccf..741b741 100644
--- a/system/database/drivers/odbc/odbc_driver.php
+++ b/system/database/drivers/odbc/odbc_driver.php
@@ -289,6 +289,24 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Update statement
+	 *
+	 * Generates a platform-specific update string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @param	array	the update data
+	 * @return	string
+         */
+	protected function _update($table, $values)
+	{
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Truncate statement
 	 *
 	 * Generates a platform-specific truncate string from the supplied data
@@ -307,6 +325,22 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Delete statement
+	 *
+	 * Generates a platform-specific delete string from the supplied data
+	 *
+	 * @param	string	the table name
+	 * @return	string
+	 */
+	protected function _delete($table)
+	{
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Close DB Connection
 	 *
 	 * @return	void
diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php
index 0ffe3bc..f4509b1 100644
--- a/system/database/drivers/pdo/pdo_driver.php
+++ b/system/database/drivers/pdo/pdo_driver.php
@@ -356,14 +356,12 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
-		$where = ($where !== '' && count($where) >=1) ? implode(' ', $where).' AND ' : '';
-
 		foreach ($values as $key => $val)
 		{
 			$ids[] = $val[$index];
@@ -377,9 +375,7 @@
 			}
 		}
 
-		$sql   = 'UPDATE '.$table.' SET ';
 		$cases = '';
-
 		foreach ($final as $k => $v)
 		{
 			$cases .= $k.' = CASE '."\n";
@@ -392,10 +388,9 @@
 			$cases .= 'ELSE '.$k.' END, ';
 		}
 
-		$sql .= substr($cases, 0, -2);
-		$sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
 
-		return $sql;
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php b/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php
index efc0500..438d312 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php
@@ -136,27 +136,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -167,21 +153,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return 'DELETE FROM '.$table.$conditions;
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -192,13 +169,11 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.$limit.($offset ? ' OFFSET '.$offset : '');
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
 	}
 
 }
diff --git a/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php
index cb18a5c..d2a484d 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php
@@ -129,10 +129,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -156,9 +156,9 @@
 				.'ELSE '.$k.' END), ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php
index 7c4b008..782bb53 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php
@@ -159,27 +159,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -190,23 +176,16 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -217,27 +196,25 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// As of SQL Server 2005 (9.0.*) ROW_NUMBER() is supported,
 		// however an ORDER BY clause is required for it to work
-		if (version_compare($this->version(), '9', '>=') && $offset && ! empty($this->qb_orderby))
+		if (version_compare($this->version(), '9', '>=') && $this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
 			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
 			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
 				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
index 6fba764..32d1f21 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php
@@ -145,29 +145,12 @@
 	 *
 	 * @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 (ignored)
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr)
-			.$where
-			.(count($orderby) > 0 ? ' ORDER BY '.implode(', ', $orderby) : '');
+		$this->qb_limit = FALSE;
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -196,19 +179,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -219,22 +195,20 @@
 	 * 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)
+	protected function _limit($sql)
 	{
 		// Limit clause depends on if Interbase or Firebird
 		if (stripos($this->version(), 'firebird') !== FALSE)
 		{
-			$select = 'FIRST '. (int) $limit
-				.($offset > 0 ? ' SKIP '. (int) $offset : '');
+			$select = 'FIRST '.$this->qb_limit
+				.($this->qb_offset > 0 ? ' SKIP '.$this->qb_offset : '');
 		}
 		else
 		{
 			$select = 'ROWS '
-				.($offset > 0 ? (int) $offset.' TO '.($limit + $offset) : (int) $limit);
+				.($this->qb_offset > 0 ? $this->qb_offset.' TO '.($this->qb_limit + $this->qb_offset) : $this->qb_limit);
 		}
 
 		return preg_replace('`SELECT`i', 'SELECT '.$select, $sql);
diff --git a/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php b/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php
index 399182e..22a5f92 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php
@@ -171,27 +171,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -202,21 +188,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return 'DELETE FROM '.$table.$conditions;
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php b/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php
index 0281215..8dd4301 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php
@@ -165,27 +165,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -214,21 +200,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return 'DELETE FROM '.$table.$conditions;
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -239,13 +216,11 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		$select = 'SELECT '.($offset ? 'SKIP '.$offset : '').'FIRST '.$limit.' ';
+		$select = 'SELECT '.($this->qb_offset ? 'SKIP '.$this->qb_offset : '').'FIRST '.$this->qb_limit.' ';
 		return preg_replace('/^(SELECT\s)/i', $select, $sql, 1);
 	}
 
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
index 4244688..9ffb93e 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php
@@ -157,10 +157,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -184,9 +184,9 @@
 				.'ELSE '.$k.' END), ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php b/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php
index d584d1f..b03218f 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php
@@ -174,20 +174,17 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			$this->where('rownum <= ',$this->qb_limit, FALSE);
+			$this->qb_limit = FALSE;
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-		empty($limit) OR $conditions[] = 'rownum <= '.$limit;
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -198,14 +195,12 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($offset + $limit + 1).')'
-			.($offset ? ' WHERE rnum >= '.($offset + 1): '');
+		return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM ('.$sql.') inner_query WHERE rownum < '.($this->qb_offset + $this->qb_limit + 1).')'
+			.($this->qb_offset ? ' WHERE rnum >= '.($this->qb_offset + 1): '');
 	}
 
 }
diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
index e7ab3fd..5944d55 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php
@@ -162,27 +162,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -211,21 +197,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return 'DELETE FROM '.$table.$conditions;
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -236,13 +213,11 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
+		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$this->qb_limit.' ', $sql);
 	}
 
 }
diff --git a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
index 2a68781..74d56e6 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php
@@ -148,27 +148,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -180,10 +166,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -202,14 +188,14 @@
 		$cases = '';
 		foreach ($final as $k => $v)
 		{
-			$cases .= $k.' = (CASE '.$k."\n"
+			$cases .= $k.' = (CASE '.$index."\n"
 				.implode("\n", $v)."\n"
 				.'ELSE '.$k.' END), ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
@@ -220,19 +206,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -243,29 +222,31 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.$limit.($offset ? ' OFFSET '.$offset : '');
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where
+	 * WHERE, HAVING
 	 *
-	 * Called by where() or or_where()
+	 * Called by where(), or_where(), having(), or_having()
 	 *
+	 * @param	string	'qb_where' or 'qb_having'
 	 * @param	mixed
 	 * @param	mixed
 	 * @param	string
+	 * @param	bool
 	 * @return	object
 	 */
-	protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
+	protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL)
 	{
+		$qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where';
+
 		if ( ! is_array($key))
 		{
 			$key = array($key => $value);
@@ -276,14 +257,10 @@
 
 		foreach ($key as $k => $v)
 		{
-			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			$prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0)
 				? $this->_group_get_type('')
 				: $this->_group_get_type($type);
 
-			$k = (($op = $this->_get_operator($k)) !== FALSE)
-				? $this->protect_identifiers(substr($k, 0, strpos($k, $op)), FALSE, $escape).strstr($k, $op)
-				: $this->protect_identifiers($k, FALSE, $escape);
-
 			if (is_null($v) && ! $this->_has_operator($k))
 			{
 				// value appears not to have been set, assign the test to IS NULL
@@ -292,13 +269,13 @@
 
 			if ( ! is_null($v))
 			{
-				if ($escape === TRUE)
+				if (is_bool($v))
 				{
-					$v = ' '.$this->escape($v);
+					$v = ' '.($v ? 'TRUE' : 'FALSE');
 				}
-				elseif (is_bool($v))
+				elseif ($escape === TRUE)
 				{
-					$v = ($v ? ' TRUE' : ' FALSE');
+					$v = ' '.(is_int($v) ? $v : $this->escape($v));
 				}
 
 				if ( ! $this->_has_operator($k))
@@ -307,11 +284,11 @@
 				}
 			}
 
-			$this->qb_where[] = $prefix.$k.$v;
+			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_where[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'where';
+				$this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+				$this->qb_cache_exists[] = substr($qb_key, 3);
 			}
 
 		}
diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php
index fd1c4b2..5b1cd97 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php
@@ -188,27 +188,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
          */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -219,23 +205,16 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -246,32 +225,30 @@
 	 * 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)
+	protected function _limit($sql)
 	{
 		// As of SQL Server 2012 (11.0.*) OFFSET is supported
 		if (version_compare($this->version(), '11', '>='))
 		{
-			return $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY';
+			return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
 		}
 
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// An ORDER BY clause is required for ROW_NUMBER() to work
-		if ($offset && ! empty($this->qb_orderby))
+		if ($this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
 			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
 			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
 				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);
diff --git a/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php
index 2a91a89..19f384c 100644
--- a/system/database/drivers/postgre/postgre_driver.php
+++ b/system/database/drivers/postgre/postgre_driver.php
@@ -463,27 +463,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -495,10 +481,10 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
+	 * @param	string	the where key
 	 * @return	string
 	 */
-	protected function _update_batch($table, $values, $index, $where = NULL)
+	protected function _update_batch($table, $values, $index)
 	{
 		$ids = array();
 		foreach ($values as $key => $val)
@@ -522,9 +508,9 @@
 				.'ELSE '.$k.' END), ';
 		}
 
-		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2)
-			.' WHERE '.(($where !== '' && count($where) > 0) ? implode(' ', $where).' AND ' : '')
-			.$index.' IN('.implode(',', $ids).')';
+		$this->where($index.' IN('.implode(',', $ids).')', NULL, FALSE);
+
+		return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where');
 	}
 
 	// --------------------------------------------------------------------
@@ -535,19 +521,12 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause (ignored)
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
-
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		return 'DELETE FROM '.$table.(count($conditions) > 0 ? ' WHERE '.implode(' AND ', $conditions) : '');
+		$this->qb_limit = FALSE;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -558,30 +537,31 @@
 	 * 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)
+	protected function _limit($sql)
 	{
-		return $sql.' LIMIT '.$limit.($offset ? ' OFFSET '.$offset : '');
+		return $sql.' LIMIT '.$this->qb_limit.($this->qb_offset ? ' OFFSET '.$this->qb_offset : '');
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * Where
+	 * WHERE, HAVING
 	 *
-	 * Called by where() or or_where()
+	 * Called by where(), or_where(), having(), or_having()
 	 *
+	 * @param	string	'qb_where' or 'qb_having'
 	 * @param	mixed
 	 * @param	mixed
 	 * @param	string
-	 * @param	mixed
+	 * @param	bool
 	 * @return	object
 	 */
-	protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
+	protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL)
 	{
+		$qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where';
+
 		if ( ! is_array($key))
 		{
 			$key = array($key => $value);
@@ -592,17 +572,10 @@
 
 		foreach ($key as $k => $v)
 		{
-			$prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0)
+			$prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0)
 				? $this->_group_get_type('')
 				: $this->_group_get_type($type);
 
-			if ($escape === TRUE)
-			{
-				$k = (($op = $this->_get_operator($k)) !== FALSE)
-					? $this->escape_identifiers(trim(substr($k, 0, strpos($k, $op)))).' '.strstr($k, $op)
-					: $this->escape_identifiers(trim($k));
-			}
-
 			if (is_null($v) && ! $this->_has_operator($k))
 			{
 				// value appears not to have been set, assign the test to IS NULL
@@ -611,13 +584,13 @@
 
 			if ( ! is_null($v))
 			{
-				if ($escape === TRUE)
+				if (is_bool($v))
 				{
-					$v = ' '.$this->escape($v);
+					$v = ' '.($v ? 'TRUE' : 'FALSE');
 				}
-				elseif (is_bool($v))
+				elseif ($escape === TRUE)
 				{
-					$v = ($v ? ' TRUE' : ' FALSE');
+					$v = ' '.(is_int($v) ? $v : $this->escape($v));
 				}
 
 				if ( ! $this->_has_operator($k))
@@ -626,11 +599,11 @@
 				}
 			}
 
-			$this->qb_where[] = $prefix.$k.$v;
+			$this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
 			if ($this->qb_caching === TRUE)
 			{
-				$this->qb_cache_where[] = $prefix.$k.$v;
-				$this->qb_cache_exists[] = 'where';
+				$this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape);
+				$this->qb_cache_exists[] = substr($qb_key, 3);
 			}
 
 		}
diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php
index 3ffbd4c..6594292 100644
--- a/system/database/drivers/sqlsrv/sqlsrv_driver.php
+++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php
@@ -364,27 +364,13 @@
 	 *
 	 * @param	string	the table name
 	 * @param	array	the update data
-	 * @param	array	the where clause
-	 * @param	array	the orderby clause (ignored)
-	 * @param	array	the limit clause (ignored)
-	 * @param	array	the like clause
 	 * @return	string
 	 */
-	protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE, $like = array())
+	protected function _update($table, $values)
 	{
-		foreach ($values as $key => $val)
-		{
-			$valstr[] = $key.' = '.$val;
-		}
-
-		$where = empty($where) ? '' : ' WHERE '.implode(' ', $where);
-
-		if ( ! empty($like))
-		{
-			$where .= ($where === '' ? ' WHERE ' : ' AND ').implode(' ', $like);
-		}
-
-		return 'UPDATE '.$table.' SET '.implode(', ', $valstr).$where;
+		$this->qb_limit = FALSE;
+		$this->qb_orderby = array();
+		return parent::_update($table, $values);
 	}
 
 	// --------------------------------------------------------------------
@@ -413,23 +399,16 @@
 	 * Generates a platform-specific delete string from the supplied data
 	 *
 	 * @param	string	the table name
-	 * @param	array	the where clause
-	 * @param	array	the like clause
-	 * @param	string	the limit clause
 	 * @return	string
 	 */
-	protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
+	protected function _delete($table)
 	{
-		$conditions = array();
+		if ($this->qb_limit)
+		{
+			return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete';
+		}
 
-		empty($where) OR $conditions[] = implode(' ', $where);
-		empty($like) OR $conditions[] = implode(' ', $like);
-
-		$conditions = (count($conditions) > 0) ? ' WHERE '.implode(' AND ', $conditions) : '';
-
-		return ($limit)
-			? 'WITH ci_delete AS (SELECT TOP '.$limit.' * FROM '.$table.$conditions.') DELETE FROM ci_delete'
-			: 'DELETE FROM '.$table.$conditions;
+		return parent::_delete($table);
 	}
 
 	// --------------------------------------------------------------------
@@ -440,32 +419,30 @@
 	 * 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)
+	protected function _limit($sql)
 	{
 		// As of SQL Server 2012 (11.0.*) OFFSET is supported
 		if (version_compare($this->version(), '11', '>='))
 		{
-			return $sql.' OFFSET '.(int) $offset.' ROWS FETCH NEXT '.(int) $limit.' ROWS ONLY';
+			return $sql.' OFFSET '.(int) $this->qb_offset.' ROWS FETCH NEXT '.$this->qb_limit.' ROWS ONLY';
 		}
 
-		$limit = $offset + $limit;
+		$limit = $this->qb_offset + $this->qb_limit;
 
 		// An ORDER BY clause is required for ROW_NUMBER() to work
-		if ($offset && ! empty($this->qb_orderby))
+		if ($this->qb_offset && ! empty($this->qb_orderby))
 		{
-			$orderby = 'ORDER BY '.implode(', ', $this->qb_orderby);
+			$orderby = $this->_compile_order_by();
 
 			// We have to strip the ORDER BY clause
 			$sql = trim(substr($sql, 0, strrpos($sql, $orderby)));
 
 			return 'SELECT '.(count($this->qb_select) === 0 ? '*' : implode(', ', $this->qb_select))." FROM (\n"
-				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.$orderby.') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
+				.preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql)
 				."\n) ".$this->escape_identifiers('CI_subquery')
-				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.((int) $offset + 1).' AND '.$limit;
+				."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit;
 		}
 
 		return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql);