From 2188ece107d4a813168b5fb14e643ee4e0590acd Mon Sep 17 00:00:00 2001
From: Qiang Xue <qiang.xue@gmail.com>
Date: Sat, 5 Jan 2013 21:20:13 -0500
Subject: [PATCH] refactoring DB

---
 framework/db/ActiveQuery.php             |  11 +++++++----
 framework/db/ActiveRelation.php          |   6 ++++--
 framework/db/BaseQuery.php               | 555 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 framework/db/Command.php                 | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 framework/db/Query.php                   | 557 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------
 framework/db/QueryBuilder.php            |  52 ++++++++++++++++++++++------------------------------
 framework/validators/ExistValidator.php  |  19 +++++--------------
 framework/validators/UniqueValidator.php |  34 +++++++++++-----------------------
 8 files changed, 705 insertions(+), 780 deletions(-)
 delete mode 100644 framework/db/BaseQuery.php

diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php
index 3f534f4..6a7c6e4 100644
--- a/framework/db/ActiveQuery.php
+++ b/framework/db/ActiveQuery.php
@@ -13,12 +13,11 @@ namespace yii\db;
 use yii\db\Connection;
 use yii\db\Command;
 use yii\db\QueryBuilder;
-use yii\db\BaseQuery;
 use yii\base\VectorIterator;
 use yii\db\Expression;
 use yii\db\Exception;
 
-class ActiveQuery extends BaseQuery
+class ActiveQuery extends Query
 {
 	/**
 	 * @var string the name of the ActiveRecord class.
@@ -125,13 +124,17 @@ class ActiveQuery extends BaseQuery
 
 	/**
 	 * Creates a DB command that can be used to execute this query.
+	 * @param Connection $db the DB connection used to create the DB command.
+	 * If null, the DB connection returned by [[modelClass]] will be used.
 	 * @return Command the created DB command instance.
 	 */
-	public function createCommand()
+	public function createCommand($db = null)
 	{
 		/** @var $modelClass ActiveRecord */
 		$modelClass = $this->modelClass;
-		$db = $modelClass::getDbConnection();
+		if ($db === null) {
+			$db = $modelClass::getDbConnection();
+		}
 		if ($this->sql === null) {
 			if ($this->from === null) {
 				$tableName = $modelClass::tableName();
diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php
index 013df4b..0eb8dda 100644
--- a/framework/db/ActiveRelation.php
+++ b/framework/db/ActiveRelation.php
@@ -96,9 +96,11 @@ class ActiveRelation extends ActiveQuery
 
 	/**
 	 * Creates a DB command that can be used to execute this query.
+	 * @param Connection $db the DB connection used to create the DB command.
+	 * If null, the DB connection returned by [[modelClass]] will be used.
 	 * @return Command the created DB command instance.
 	 */
-	public function createCommand()
+	public function createCommand($db = null)
 	{
 		if ($this->primaryModel !== null) {
 			// lazy loading
@@ -120,7 +122,7 @@ class ActiveRelation extends ActiveQuery
 				$this->filterByModels(array($this->primaryModel));
 			}
 		}
-		return parent::createCommand();
+		return parent::createCommand($db);
 	}
 
 	public function findWith($name, &$primaryModels)
diff --git a/framework/db/BaseQuery.php b/framework/db/BaseQuery.php
deleted file mode 100644
index 58b8f57..0000000
--- a/framework/db/BaseQuery.php
+++ /dev/null
@@ -1,555 +0,0 @@
-<?php
-/**
- * BaseQuery class file.
- *
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2012 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\db;
-
-/**
- * BaseQuery is the base class that represents a SQL SELECT statement in a DBMS-independent way.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class BaseQuery extends \yii\base\Component
-{
-	/**
-	 * @var string|array the columns being selected. This refers to the SELECT clause in a SQL
-	 * statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
-	 * If not set, if means all columns.
-	 * @see select()
-	 */
-	public $select;
-	/**
-	 * @var string additional option that should be appended to the 'SELECT' keyword. For example,
-	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
-	 */
-	public $selectOption;
-	/**
-	 * @var boolean whether to select distinct rows of data only. If this is set true,
-	 * the SELECT clause would be changed to SELECT DISTINCT.
-	 */
-	public $distinct;
-	/**
-	 * @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
-	 * It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
-	 * @see from()
-	 */
-	public $from;
-	/**
-	 * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
-	 * For example, `age > 31 AND team = 1`.
-	 * @see where()
-	 */
-	public $where;
-	/**
-	 * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
-	 */
-	public $limit;
-	/**
-	 * @var integer zero-based offset from where the records are to be returned. If not set or
-	 * less than 0, it means starting from the beginning.
-	 */
-	public $offset;
-	/**
-	 * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement.
-	 * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`).
-	 */
-	public $orderBy;
-	/**
-	 * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement.
-	 * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
-	 */
-	public $groupBy;
-	/**
-	 * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement.
-	 * It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
-	 * `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
-	 * @see join()
-	 */
-	public $join;
-	/**
-	 * @var string|array the condition to be applied in the GROUP BY clause.
-	 * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
-	 */
-	public $having;
-	/**
-	 * @var string|BaseQuery[] the UNION clause(s) in a SQL statement. This can be either a string
-	 * representing a single UNION clause or an array representing multiple UNION clauses.
-	 * Each union clause can be a string or a `BaseQuery` object which refers to the SQL statement.
-	 */
-	public $union;
-	/**
-	 * @var array list of query parameter values indexed by parameter placeholders.
-	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
-	 */
-	public $params;
-
-	/**
-	 * Sets the SELECT part of the query.
-	 * @param string|array $columns the columns to be selected.
-	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
-	 * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
-	 * The method will automatically quote the column names unless a column contains some parenthesis
-	 * (which means the column contains a DB expression).
-	 * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
-	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
-	 * @return BaseQuery the query object itself
-	 */
-	public function select($columns, $option = null)
-	{
-		$this->select = $columns;
-		$this->selectOption = $option;
-		return $this;
-	}
-
-	/**
-	 * Sets the value indicating whether to SELECT DISTINCT or not.
-	 * @param bool $value whether to SELECT DISTINCT or not.
-	 * @return BaseQuery the query object itself
-	 */
-	public function distinct($value = true)
-	{
-		$this->distinct = $value;
-		return $this;
-	}
-
-	/**
-	 * Sets the FROM part of the query.
-	 * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`)
-	 * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names.
-	 * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`).
-	 * The method will automatically quote the table names unless it contains some parenthesis
-	 * (which means the table is given as a sub-query or DB expression).
-	 * @return BaseQuery the query object itself
-	 */
-	public function from($tables)
-	{
-		$this->from = $tables;
-		return $this;
-	}
-
-	/**
-	 * Sets the WHERE part of the query.
-	 *
-	 * The method requires a $condition parameter, and optionally a $params parameter
-	 * specifying the values to be bound to the query.
-	 *
-	 * The $condition parameter should be either a string (e.g. 'id=1') or an array.
-	 * If the latter, it must be in one of the following two formats:
-	 *
-	 * - hash format: `array('column1' => value1, 'column2' => value2, ...)`
-	 * - operator format: `array(operator, operand1, operand2, ...)`
-	 *
-	 * A condition in hash format represents the following SQL expression in general:
-	 * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
-	 * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
-	 * in the generated expression. Below are some examples:
-	 *
-	 * - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`.
-	 * - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`.
-	 * - `array('status'=>null) generates `status IS NULL`.
-	 *
-	 * A condition in operator format generates the SQL expression according to the specified operator, which
-	 * can be one of the followings:
-	 *
-	 * - `and`: the operands should be concatenated together using `AND`. For example,
-	 * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
-	 * it will be converted into a string using the rules described here. For example,
-	 * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
-	 * The method will NOT do any quoting or escaping.
-	 *
-	 * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
-	 *
-	 * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
-	 * starting and ending values of the range that the column is in.
-	 * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
-	 *
-	 * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
-	 * in the generated condition.
-	 *
-	 * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
-	 * the range of the values that the column or DB expression should be in. For example,
-	 * `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`.
-	 * The method will properly quote the column name and escape values in the range.
-	 *
-	 * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
-	 *
-	 * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
-	 * the values that the column or DB expression should be like.
-	 * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
-	 * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
-	 * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
-	 * `name LIKE '%test%' AND name LIKE '%sample%'`.
-	 * The method will properly quote the column name and escape values in the range.
-	 *
-	 * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
-	 * predicates when operand 2 is an array.
-	 *
-	 * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
-	 * in the generated condition.
-	 *
-	 * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
-	 * the `NOT LIKE` predicates.
-	 *
-	 * @param string|array $condition the conditions that should be put in the WHERE part.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see andWhere()
-	 * @see orWhere()
-	 */
-	public function where($condition, $params = array())
-	{
-		$this->where = $condition;
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Adds an additional WHERE condition to the existing one.
-	 * The new condition and the existing one will be joined using the 'AND' operator.
-	 * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
-	 * on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see where()
-	 * @see orWhere()
-	 */
-	public function andWhere($condition, $params = array())
-	{
-		if ($this->where === null) {
-			$this->where = $condition;
-		} else {
-			$this->where = array('and', $this->where, $condition);
-		}
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Adds an additional WHERE condition to the existing one.
-	 * The new condition and the existing one will be joined using the 'OR' operator.
-	 * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
-	 * on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see where()
-	 * @see andWhere()
-	 */
-	public function orWhere($condition, $params = array())
-	{
-		if ($this->where === null) {
-			$this->where = $condition;
-		} else {
-			$this->where = array('or', $this->where, $condition);
-		}
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Appends a JOIN part to the query.
-	 * The first parameter specifies what type of join it is.
-	 * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
-	 * @param string $table the table to be joined.
-	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
-	 * The method will automatically quote the table name unless it contains some parenthesis
-	 * (which means the table is given as a sub-query or DB expression).
-	 * @param string|array $on the join condition that should appear in the ON part.
-	 * Please refer to [[where()]] on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 */
-	public function join($type, $table, $on = '', $params = array())
-	{
-		$this->join[] = array($type, $table, $on);
-		return $this->addParams($params);
-	}
-
-	/**
-	 * Appends an INNER JOIN part to the query.
-	 * @param string $table the table to be joined.
-	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
-	 * The method will automatically quote the table name unless it contains some parenthesis
-	 * (which means the table is given as a sub-query or DB expression).
-	 * @param string|array $on the join condition that should appear in the ON part.
-	 * Please refer to [[where()]] on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 */
-	public function innerJoin($table, $on = '', $params = array())
-	{
-		$this->join[] = array('INNER JOIN', $table, $on);
-		return $this->addParams($params);
-	}
-
-	/**
-	 * Appends a LEFT OUTER JOIN part to the query.
-	 * @param string $table the table to be joined.
-	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
-	 * The method will automatically quote the table name unless it contains some parenthesis
-	 * (which means the table is given as a sub-query or DB expression).
-	 * @param string|array $on the join condition that should appear in the ON part.
-	 * Please refer to [[where()]] on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query
-	 * @return BaseQuery the query object itself
-	 */
-	public function leftJoin($table, $on = '', $params = array())
-	{
-		$this->join[] = array('LEFT JOIN', $table, $on);
-		return $this->addParams($params);
-	}
-
-	/**
-	 * Appends a RIGHT OUTER JOIN part to the query.
-	 * @param string $table the table to be joined.
-	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
-	 * The method will automatically quote the table name unless it contains some parenthesis
-	 * (which means the table is given as a sub-query or DB expression).
-	 * @param string|array $on the join condition that should appear in the ON part.
-	 * Please refer to [[where()]] on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query
-	 * @return BaseQuery the query object itself
-	 */
-	public function rightJoin($table, $on = '', $params = array())
-	{
-		$this->join[] = array('RIGHT JOIN', $table, $on);
-		return $this->addParams($params);
-	}
-
-	/**
-	 * Sets the GROUP BY part of the query.
-	 * @param string|array $columns the columns to be grouped by.
-	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
-	 * The method will automatically quote the column names unless a column contains some parenthesis
-	 * (which means the column contains a DB expression).
-	 * @return BaseQuery the query object itself
-	 * @see addGroup()
-	 */
-	public function groupBy($columns)
-	{
-		$this->groupBy = $columns;
-		return $this;
-	}
-
-	/**
-	 * Adds additional group-by columns to the existing ones.
-	 * @param string|array $columns additional columns to be grouped by.
-	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
-	 * The method will automatically quote the column names unless a column contains some parenthesis
-	 * (which means the column contains a DB expression).
-	 * @return BaseQuery the query object itself
-	 * @see group()
-	 */
-	public function addGroup($columns)
-	{
-		if (empty($this->groupBy)) {
-			$this->groupBy = $columns;
-		} else {
-			if (!is_array($this->groupBy)) {
-				$this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			if (!is_array($columns)) {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			$this->groupBy = array_merge($this->groupBy, $columns);
-		}
-		return $this;
-	}
-
-	/**
-	 * Sets the HAVING part of the query.
-	 * @param string|array $condition the conditions to be put after HAVING.
-	 * Please refer to [[where()]] on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see andHaving()
-	 * @see orHaving()
-	 */
-	public function having($condition, $params = array())
-	{
-		$this->having = $condition;
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Adds an additional HAVING condition to the existing one.
-	 * The new condition and the existing one will be joined using the 'AND' operator.
-	 * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
-	 * on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see having()
-	 * @see orHaving()
-	 */
-	public function andHaving($condition, $params = array())
-	{
-		if ($this->having === null) {
-			$this->having = $condition;
-		} else {
-			$this->having = array('and', $this->having, $condition);
-		}
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Adds an additional HAVING condition to the existing one.
-	 * The new condition and the existing one will be joined using the 'OR' operator.
-	 * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
-	 * on how to specify this parameter.
-	 * @param array $params the parameters (name=>value) to be bound to the query.
-	 * @return BaseQuery the query object itself
-	 * @see having()
-	 * @see andHaving()
-	 */
-	public function orHaving($condition, $params = array())
-	{
-		if ($this->having === null) {
-			$this->having = $condition;
-		} else {
-			$this->having = array('or', $this->having, $condition);
-		}
-		$this->addParams($params);
-		return $this;
-	}
-
-	/**
-	 * Sets the ORDER BY part of the query.
-	 * @param string|array $columns the columns (and the directions) to be ordered by.
-	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
-	 * The method will automatically quote the column names unless a column contains some parenthesis
-	 * (which means the column contains a DB expression).
-	 * @return BaseQuery the query object itself
-	 * @see addOrder()
-	 */
-	public function orderBy($columns)
-	{
-		$this->orderBy = $columns;
-		return $this;
-	}
-
-	/**
-	 * Adds additional ORDER BY columns to the query.
-	 * @param string|array $columns the columns (and the directions) to be ordered by.
-	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
-	 * The method will automatically quote the column names unless a column contains some parenthesis
-	 * (which means the column contains a DB expression).
-	 * @return BaseQuery the query object itself
-	 * @see order()
-	 */
-	public function addOrderBy($columns)
-	{
-		if (empty($this->orderBy)) {
-			$this->orderBy = $columns;
-		} else {
-			if (!is_array($this->orderBy)) {
-				$this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			if (!is_array($columns)) {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			$this->orderBy = array_merge($this->orderBy, $columns);
-		}
-		return $this;
-	}
-
-	/**
-	 * Sets the LIMIT part of the query.
-	 * @param integer $limit the limit
-	 * @return BaseQuery the query object itself
-	 */
-	public function limit($limit)
-	{
-		$this->limit = $limit;
-		return $this;
-	}
-
-	/**
-	 * Sets the OFFSET part of the query.
-	 * @param integer $offset the offset
-	 * @return BaseQuery the query object itself
-	 */
-	public function offset($offset)
-	{
-		$this->offset = $offset;
-		return $this;
-	}
-
-	/**
-	 * Appends a SQL statement using UNION operator.
-	 * @param string|BaseQuery $sql the SQL statement to be appended using UNION
-	 * @return BaseQuery the query object itself
-	 */
-	public function union($sql)
-	{
-		$this->union[] = $sql;
-		return $this;
-	}
-
-	/**
-	 * Sets the parameters to be bound to the query.
-	 * @param array $params list of query parameter values indexed by parameter placeholders.
-	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
-	 * @return BaseQuery the query object itself
-	 * @see addParams()
-	 */
-	public function params($params)
-	{
-		$this->params = $params;
-		return $this;
-	}
-
-	/**
-	 * Adds additional parameters to be bound to the query.
-	 * @param array $params list of query parameter values indexed by parameter placeholders.
-	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
-	 * @return BaseQuery the query object itself
-	 * @see params()
-	 */
-	public function addParams($params)
-	{
-		if ($params !== array()) {
-			if ($this->params === null) {
-				$this->params = $params;
-			} else {
-				foreach ($params as $name => $value) {
-					if (is_integer($name)) {
-						$this->params[] = $value;
-					} else {
-						$this->params[$name] = $value;
-					}
-				}
-			}
-		}
-		return $this;
-	}
-
-	/**
-	 * Merges this query with another one.
-	 * If a property of `$query` is not null, it will be used to overwrite
-	 * the corresponding property of `$this`.
-	 * @param BaseQuery $query the new query to be merged with this query.
-	 * @return BaseQuery the query object itself
-	 */
-	public function mergeWith(BaseQuery $query)
-	{
-		$properties = array(
-			'select', 'selectOption', 'distinct', 'from',
-			'where', 'limit', 'offset', 'orderBy', 'groupBy',
-			'join', 'having', 'union', 'params',
-		);
-		// todo: incorrect, do we need it? should we provide a configure() method instead?
-		foreach ($properties as $name => $value) {
-			if ($value !== null) {
-				$this->$name = $value;
-			}
-		}
-		return $this;
-	}
-}
diff --git a/framework/db/Command.php b/framework/db/Command.php
index c5b15ee..8ae7912 100644
--- a/framework/db/Command.php
+++ b/framework/db/Command.php
@@ -366,6 +366,7 @@ class Command extends \yii\base\Component
 
 		\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
 
+		/** @var $cache \yii\caching\Cache */
 		if ($db->enableQueryCache && $method !== '') {
 			$cache = \Yii::$application->getComponent($db->queryCacheID);
 		}
@@ -404,7 +405,7 @@ class Command extends \yii\base\Component
 				\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 
-			if (isset($cache)) {
+			if (isset($cache, $cacheKey)) {
 				$cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
 				\Yii::trace('Saved query result in cache', __CLASS__);
 			}
@@ -420,4 +421,252 @@ class Command extends \yii\base\Component
 			throw new Exception($message, (int)$e->getCode(), $errorInfo);
 		}
 	}
+
+	/**
+	 * Creates an INSERT command.
+	 * For example,
+	 *
+	 * ~~~
+	 * $db->createCommand()->insert('tbl_user', array(
+	 *	 'name' => 'Sam',
+	 *	 'age' => 30,
+	 * ))->execute();
+	 * ~~~
+	 *
+	 * The method will properly escape the column names, and bind the values to be inserted.
+	 *
+	 * Note that the created command is not executed until [[execute()]] is called.
+	 *
+	 * @param string $table the table that new rows will be inserted into.
+	 * @param array $columns the column data (name=>value) to be inserted into the table.
+	 * @param array $params the parameters to be bound to the command
+	 * @return Command the command object itself
+	 */
+	public function insert($table, $columns, $params = array())
+	{
+		$sql = $this->connection->getQueryBuilder()->insert($table, $columns, $params);
+		return $this->setSql($sql)->bindValues($params);
+	}
+
+	/**
+	 * Creates an UPDATE command.
+	 * For example,
+	 *
+	 * ~~~
+	 * $db->createCommand()->update('tbl_user', array(
+	 *	 'status' => 1,
+	 * ), 'age > 30')->execute();
+	 * ~~~
+	 *
+	 * The method will properly escape the column names and bind the values to be updated.
+	 *
+	 * Note that the created command is not executed until [[execute()]] is called.
+	 *
+	 * @param string $table the table to be updated.
+	 * @param array $columns the column data (name=>value) to be updated.
+	 * @param mixed $condition the condition that will be put in the WHERE part. Please
+	 * refer to [[Query::where()]] on how to specify condition.
+	 * @param array $params the parameters to be bound to the command
+	 * @return Command the command object itself
+	 */
+	public function update($table, $columns, $condition = '', $params = array())
+	{
+		$sql = $this->connection->getQueryBuilder()->update($table, $columns, $condition, $params);
+		return $this->setSql($sql)->bindValues($params);
+	}
+
+	/**
+	 * Creates a DELETE command.
+	 * For example,
+	 *
+	 * ~~~
+	 * $db->createCommand()->delete('tbl_user', 'status = 0')->execute();
+	 * ~~~
+	 *
+	 * The method will properly escape the table and column names.
+	 *
+	 * Note that the created command is not executed until [[execute()]] is called.
+	 *
+	 * @param string $table the table where the data will be deleted from.
+	 * @param mixed $condition the condition that will be put in the WHERE part. Please
+	 * refer to [[Query::where()]] on how to specify condition.
+	 * @param array $params the parameters to be bound to the command
+	 * @return Command the command object itself
+	 */
+	public function delete($table, $condition = '', $params = array())
+	{
+		$sql = $this->connection->getQueryBuilder()->delete($table, $condition);
+		return $this->setSql($sql)->bindValues($params);
+	}
+
+
+	/**
+	 * Creates a SQL command for creating a new DB table.
+	 *
+	 * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'),
+	 * where name stands for a column name which will be properly quoted by the method, and definition
+	 * stands for the column type which can contain an abstract DB type.
+	 * The method [[\yii\db\QueryBuilder::getColumnType()]] will be called
+	 * to convert the abstract column types to physical ones. For example, `string` will be converted
+	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+	 *
+	 * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+	 * inserted into the generated SQL.
+	 *
+	 * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+	 * @param array $columns the columns (name=>definition) in the new table.
+	 * @param string $options additional SQL fragment that will be appended to the generated SQL.
+	 * @return Command the command object itself
+	 */
+	public function createTable($table, $columns, $options = null)
+	{
+		$sql = $this->connection->getQueryBuilder()->createTable($table, $columns, $options);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for renaming a DB table.
+	 * @param string $table the table to be renamed. The name will be properly quoted by the method.
+	 * @param string $newName the new table name. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function renameTable($table, $newName)
+	{
+		$sql = $this->connection->getQueryBuilder()->renameTable($table, $newName);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for dropping a DB table.
+	 * @param string $table the table to be dropped. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function dropTable($table)
+	{
+		$sql = $this->connection->getQueryBuilder()->dropTable($table);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for truncating a DB table.
+	 * @param string $table the table to be truncated. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function truncateTable($table)
+	{
+		$sql = $this->connection->getQueryBuilder()->truncateTable($table);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for adding a new DB column.
+	 * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+	 * @param string $column the name of the new column. The name will be properly quoted by the method.
+	 * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+	 * to convert the give column type to the physical one. For example, `string` will be converted
+	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+	 * @return Command the command object itself
+	 */
+	public function addColumn($table, $column, $type)
+	{
+		$sql = $this->connection->getQueryBuilder()->addColumn($table, $column, $type);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for dropping a DB column.
+	 * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+	 * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function dropColumn($table, $column)
+	{
+		$sql = $this->connection->getQueryBuilder()->dropColumn($table, $column);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for renaming a column.
+	 * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+	 * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+	 * @param string $newName the new name of the column. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function renameColumn($table, $oldName, $newName)
+	{
+		$sql = $this->connection->getQueryBuilder()->renameColumn($table, $oldName, $newName);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for changing the definition of a column.
+	 * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+	 * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+	 * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+	 * to convert the give column type to the physical one. For example, `string` will be converted
+	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+	 * @return Command the command object itself
+	 */
+	public function alterColumn($table, $column, $type)
+	{
+		$sql = $this->connection->getQueryBuilder()->alterColumn($table, $column, $type);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for adding a foreign key constraint to an existing table.
+	 * The method will properly quote the table and column names.
+	 * @param string $name the name of the foreign key constraint.
+	 * @param string $table the table that the foreign key constraint will be added to.
+	 * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+	 * @param string $refTable the table that the foreign key references to.
+	 * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+	 * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+	 * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+	 * @return Command the command object itself
+	 */
+	public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+	{
+		$sql = $this->connection->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for dropping a foreign key constraint.
+	 * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+	 * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function dropForeignKey($name, $table)
+	{
+		$sql = $this->connection->getQueryBuilder()->dropForeignKey($name, $table);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for creating a new index.
+	 * @param string $name the name of the index. The name will be properly quoted by the method.
+	 * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+	 * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+	 * by commas. The column names will be properly quoted by the method.
+	 * @param boolean $unique whether to add UNIQUE constraint on the created index.
+	 * @return Command the command object itself
+	 */
+	public function createIndex($name, $table, $columns, $unique = false)
+	{
+		$sql = $this->connection->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for dropping an index.
+	 * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+	 * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+	 * @return Command the command object itself
+	 */
+	public function dropIndex($name, $table)
+	{
+		$sql = $this->connection->getQueryBuilder()->dropIndex($name, $table);
+		return $this->setSql($sql);
+	}
 }
diff --git a/framework/db/Query.php b/framework/db/Query.php
index 2ca072a..7e9f3cb 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -23,288 +23,543 @@ namespace yii\db;
  * $query->select('id, name')
  *     ->from('tbl_user')
  *     ->limit(10);
- * // get the actual SQL statement
- * echo $query->getSql();
- * // or execute the query
+ * // build and execute the query
  * $users = $query->createCommand()->queryAll();
  * ~~~
  *
- * By calling [[getSql()]], we can obtain the actual SQL statement from a Query object.
- * And by calling [[createCommand()]], we can get a [[Command]] instance which can be further
+ * By calling [[createCommand()]], we can get a [[Command]] instance which can be further
  * used to perform/execute the DB query against a database.
  *
- * @property string $sql the SQL statement represented by this query object.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class Query extends BaseQuery
+class Query extends \yii\base\Component
 {
 	/**
-	 * @var array the operation that this query represents. This refers to the method call as well as
-	 * the corresponding parameters for constructing a non-select SQL statement (e.g. INSERT, CREATE TABLE).
-	 * This property is mainly maintained by methods such as [[insert()]], [[update()]], [[createTable()]].
-	 * If this property is not set, it means this query represents a SELECT statement.
+	 * @var string|array the columns being selected. This refers to the SELECT clause in a SQL
+	 * statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
+	 * If not set, if means all columns.
+	 * @see select()
+	 */
+	public $select;
+	/**
+	 * @var string additional option that should be appended to the 'SELECT' keyword. For example,
+	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+	 */
+	public $selectOption;
+	/**
+	 * @var boolean whether to select distinct rows of data only. If this is set true,
+	 * the SELECT clause would be changed to SELECT DISTINCT.
+	 */
+	public $distinct;
+	/**
+	 * @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
+	 * It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
+	 * @see from()
+	 */
+	public $from;
+	/**
+	 * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
+	 * For example, `age > 31 AND team = 1`.
+	 * @see where()
+	 */
+	public $where;
+	/**
+	 * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
+	 */
+	public $limit;
+	/**
+	 * @var integer zero-based offset from where the records are to be returned. If not set or
+	 * less than 0, it means starting from the beginning.
+	 */
+	public $offset;
+	/**
+	 * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement.
+	 * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`).
 	 */
-	public $operation;
+	public $orderBy;
 	/**
-	 * @var string the SQL statement that this query represents. This is directly set by user.
+	 * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement.
+	 * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
 	 */
-	public $sql;
+	public $groupBy;
+	/**
+	 * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement.
+	 * It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
+	 * `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
+	 * @see join()
+	 */
+	public $join;
+	/**
+	 * @var string|array the condition to be applied in the GROUP BY clause.
+	 * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
+	 */
+	public $having;
+	/**
+	 * @var string|Query[] the UNION clause(s) in a SQL statement. This can be either a string
+	 * representing a single UNION clause or an array representing multiple UNION clauses.
+	 * Each union clause can be a string or a `Query` object which refers to the SQL statement.
+	 */
+	public $union;
+	/**
+	 * @var array list of query parameter values indexed by parameter placeholders.
+	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
+	 */
+	public $params;
 
 	/**
-	 * Generates and returns the SQL statement according to this query.
-	 * Note that after calling this method, [[params]] may be modified with additional
-	 * parameters generated by the query builder.
-	 * @param Connection $connection the database connection used to generate the SQL statement.
+	 * Creates a DB command that can be used to execute this query.
+	 * @param Connection $db the database connection used to generate the SQL statement.
 	 * If this parameter is not given, the `db` application component will be used.
-	 * @return string the generated SQL statement
+	 * @return Command the created DB command instance.
 	 */
-	public function getSql($connection = null)
+	public function createCommand($db = null)
 	{
-		if ($this->sql !== null) {
-			return $this->sql;
-		}
-		if ($connection === null) {
-			$connection = \Yii::$application->db;
-		}
-		$qb = $connection->getQueryBuilder();
-		if ($this->operation !== null) {
-			$params = $this->operation;
-			$method = array_shift($params);
-			$qb->query = $this;
-			return call_user_func_array(array($qb, $method), $params);
-		} else {
-			/** @var $qb QueryBuilder */
-			return $qb->build($this);
+		if ($db === null) {
+			$db = \Yii::$application->db;
 		}
+		$sql = $db->getQueryBuilder()->build($this);
+		return $db->createCommand($sql, $this->params);
 	}
 
 	/**
-	 * Creates a DB command that can be used to execute this query.
-	 * @param Connection $connection the database connection used to generate the SQL statement.
-	 * If this parameter is not given, the `db` application component will be used.
-	 * @return Command the created DB command instance.
+	 * Sets the SELECT part of the query.
+	 * @param string|array $columns the columns to be selected.
+	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+	 * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
+	 * The method will automatically quote the column names unless a column contains some parenthesis
+	 * (which means the column contains a DB expression).
+	 * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
+	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+	 * @return Query the query object itself
+	 */
+	public function select($columns, $option = null)
+	{
+		$this->select = $columns;
+		$this->selectOption = $option;
+		return $this;
+	}
+
+	/**
+	 * Sets the value indicating whether to SELECT DISTINCT or not.
+	 * @param bool $value whether to SELECT DISTINCT or not.
+	 * @return Query the query object itself
+	 */
+	public function distinct($value = true)
+	{
+		$this->distinct = $value;
+		return $this;
+	}
+
+	/**
+	 * Sets the FROM part of the query.
+	 * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`)
+	 * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names.
+	 * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`).
+	 * The method will automatically quote the table names unless it contains some parenthesis
+	 * (which means the table is given as a sub-query or DB expression).
+	 * @return Query the query object itself
+	 */
+	public function from($tables)
+	{
+		$this->from = $tables;
+		return $this;
+	}
+
+	/**
+	 * Sets the WHERE part of the query.
+	 *
+	 * The method requires a $condition parameter, and optionally a $params parameter
+	 * specifying the values to be bound to the query.
+	 *
+	 * The $condition parameter should be either a string (e.g. 'id=1') or an array.
+	 * If the latter, it must be in one of the following two formats:
+	 *
+	 * - hash format: `array('column1' => value1, 'column2' => value2, ...)`
+	 * - operator format: `array(operator, operand1, operand2, ...)`
+	 *
+	 * A condition in hash format represents the following SQL expression in general:
+	 * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
+	 * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
+	 * in the generated expression. Below are some examples:
+	 *
+	 * - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`.
+	 * - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`.
+	 * - `array('status'=>null) generates `status IS NULL`.
+	 *
+	 * A condition in operator format generates the SQL expression according to the specified operator, which
+	 * can be one of the followings:
+	 *
+	 * - `and`: the operands should be concatenated together using `AND`. For example,
+	 * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
+	 * it will be converted into a string using the rules described here. For example,
+	 * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
+	 * The method will NOT do any quoting or escaping.
+	 *
+	 * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
+	 *
+	 * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
+	 * starting and ending values of the range that the column is in.
+	 * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
+	 *
+	 * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
+	 * in the generated condition.
+	 *
+	 * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
+	 * the range of the values that the column or DB expression should be in. For example,
+	 * `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`.
+	 * The method will properly quote the column name and escape values in the range.
+	 *
+	 * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
+	 *
+	 * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
+	 * the values that the column or DB expression should be like.
+	 * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
+	 * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
+	 * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
+	 * `name LIKE '%test%' AND name LIKE '%sample%'`.
+	 * The method will properly quote the column name and escape values in the range.
+	 *
+	 * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
+	 * predicates when operand 2 is an array.
+	 *
+	 * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
+	 * in the generated condition.
+	 *
+	 * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
+	 * the `NOT LIKE` predicates.
+	 *
+	 * @param string|array $condition the conditions that should be put in the WHERE part.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
+	 * @return Query the query object itself
+	 * @see andWhere()
+	 * @see orWhere()
 	 */
-	public function createCommand($connection = null)
+	public function where($condition, $params = array())
 	{
-		if ($connection === null) {
-			$connection = \Yii::$application->db;
+		$this->where = $condition;
+		$this->addParams($params);
+		return $this;
+	}
+
+	/**
+	 * Adds an additional WHERE condition to the existing one.
+	 * The new condition and the existing one will be joined using the 'AND' operator.
+	 * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+	 * on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
+	 * @return Query the query object itself
+	 * @see where()
+	 * @see orWhere()
+	 */
+	public function andWhere($condition, $params = array())
+	{
+		if ($this->where === null) {
+			$this->where = $condition;
+		} else {
+			$this->where = array('and', $this->where, $condition);
 		}
-		$sql = $this->getSql($connection);
-		return $connection->createCommand($sql, $this->params);
+		$this->addParams($params);
+		return $this;
 	}
 
 	/**
-	 * Creates and executes an INSERT SQL statement.
-	 * The method will properly escape the column names, and bind the values to be inserted.
-	 * @param string $table the table that new rows will be inserted into.
-	 * @param array $columns the column data (name=>value) to be inserted into the table.
+	 * Adds an additional WHERE condition to the existing one.
+	 * The new condition and the existing one will be joined using the 'OR' operator.
+	 * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+	 * on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
+	 * @see where()
+	 * @see andWhere()
 	 */
-	public function insert($table, $columns)
+	public function orWhere($condition, $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table, $columns);
+		if ($this->where === null) {
+			$this->where = $condition;
+		} else {
+			$this->where = array('or', $this->where, $condition);
+		}
+		$this->addParams($params);
 		return $this;
 	}
 
 	/**
-	 * Creates and executes an UPDATE SQL statement.
-	 * The method will properly escape the column names and bind the values to be updated.
-	 * @param string $table the table to be updated.
-	 * @param array $columns the column data (name=>value) to be updated.
-	 * @param string|array $condition the conditions that will be put in the WHERE part.
+	 * Appends a JOIN part to the query.
+	 * The first parameter specifies what type of join it is.
+	 * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
+	 * @param string $table the table to be joined.
+	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+	 * The method will automatically quote the table name unless it contains some parenthesis
+	 * (which means the table is given as a sub-query or DB expression).
+	 * @param string|array $on the join condition that should appear in the ON part.
 	 * Please refer to [[where()]] on how to specify this parameter.
 	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
 	 */
-	public function update($table, $columns, $condition = '', $params = array())
+	public function join($type, $table, $on = '', $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table, $columns, $condition, $params);
-		return $this;
+		$this->join[] = array($type, $table, $on);
+		return $this->addParams($params);
 	}
 
 	/**
-	 * Creates and executes a DELETE SQL statement.
-	 * @param string $table the table where the data will be deleted from.
-	 * @param string|array $condition the conditions that will be put in the WHERE part.
+	 * Appends an INNER JOIN part to the query.
+	 * @param string $table the table to be joined.
+	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+	 * The method will automatically quote the table name unless it contains some parenthesis
+	 * (which means the table is given as a sub-query or DB expression).
+	 * @param string|array $on the join condition that should appear in the ON part.
 	 * Please refer to [[where()]] on how to specify this parameter.
 	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
 	 */
-	public function delete($table, $condition = '', $params = array())
+	public function innerJoin($table, $on = '', $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table, $condition, $params);
-		return $this;
+		$this->join[] = array('INNER JOIN', $table, $on);
+		return $this->addParams($params);
 	}
 
 	/**
-	 * Builds and executes a SQL statement for creating a new DB table.
-	 *
-	 * The columns in the new  table should be specified as name-definition pairs (e.g. 'name'=>'string'),
-	 * where name stands for a column name which will be properly quoted by the method, and definition
-	 * stands for the column type which can contain an abstract DB type.
-	 * The method [[\yii\db\QueryBuilder::getColumnType()]] will be called
-	 * to convert the abstract column types to physical ones. For example, `string` will be converted
-	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
-	 *
-	 * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
-	 * inserted into the generated SQL.
-	 *
-	 * @param string $table the name of the table to be created. The name will be properly quoted by the method.
-	 * @param array $columns the columns (name=>definition) in the new table.
-	 * @param string $options additional SQL fragment that will be appended to the generated SQL.
+	 * Appends a LEFT OUTER JOIN part to the query.
+	 * @param string $table the table to be joined.
+	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+	 * The method will automatically quote the table name unless it contains some parenthesis
+	 * (which means the table is given as a sub-query or DB expression).
+	 * @param string|array $on the join condition that should appear in the ON part.
+	 * Please refer to [[where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query
 	 * @return Query the query object itself
 	 */
-	public function createTable($table, $columns, $options = null)
+	public function leftJoin($table, $on = '', $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table, $columns, $options);
+		$this->join[] = array('LEFT JOIN', $table, $on);
+		return $this->addParams($params);
+	}
+
+	/**
+	 * Appends a RIGHT OUTER JOIN part to the query.
+	 * @param string $table the table to be joined.
+	 * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+	 * The method will automatically quote the table name unless it contains some parenthesis
+	 * (which means the table is given as a sub-query or DB expression).
+	 * @param string|array $on the join condition that should appear in the ON part.
+	 * Please refer to [[where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query
+	 * @return Query the query object itself
+	 */
+	public function rightJoin($table, $on = '', $params = array())
+	{
+		$this->join[] = array('RIGHT JOIN', $table, $on);
+		return $this->addParams($params);
+	}
+
+	/**
+	 * Sets the GROUP BY part of the query.
+	 * @param string|array $columns the columns to be grouped by.
+	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+	 * The method will automatically quote the column names unless a column contains some parenthesis
+	 * (which means the column contains a DB expression).
+	 * @return Query the query object itself
+	 * @see addGroup()
+	 */
+	public function groupBy($columns)
+	{
+		$this->groupBy = $columns;
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for renaming a DB table.
-	 * @param string $table the table to be renamed. The name will be properly quoted by the method.
-	 * @param string $newName the new table name. The name will be properly quoted by the method.
+	 * Adds additional group-by columns to the existing ones.
+	 * @param string|array $columns additional columns to be grouped by.
+	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+	 * The method will automatically quote the column names unless a column contains some parenthesis
+	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
+	 * @see group()
 	 */
-	public function renameTable($table, $newName)
+	public function addGroup($columns)
 	{
-		$this->operation = array(__FUNCTION__, $table, $newName);
+		if (empty($this->groupBy)) {
+			$this->groupBy = $columns;
+		} else {
+			if (!is_array($this->groupBy)) {
+				$this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY);
+			}
+			if (!is_array($columns)) {
+				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+			}
+			$this->groupBy = array_merge($this->groupBy, $columns);
+		}
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for dropping a DB table.
-	 * @param string $table the table to be dropped. The name will be properly quoted by the method.
+	 * Sets the HAVING part of the query.
+	 * @param string|array $condition the conditions to be put after HAVING.
+	 * Please refer to [[where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
+	 * @see andHaving()
+	 * @see orHaving()
 	 */
-	public function dropTable($table)
+	public function having($condition, $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table);
+		$this->having = $condition;
+		$this->addParams($params);
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for truncating a DB table.
-	 * @param string $table the table to be truncated. The name will be properly quoted by the method.
+	 * Adds an additional HAVING condition to the existing one.
+	 * The new condition and the existing one will be joined using the 'AND' operator.
+	 * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+	 * on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
+	 * @see having()
+	 * @see orHaving()
 	 */
-	public function truncateTable($table)
+	public function andHaving($condition, $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table);
+		if ($this->having === null) {
+			$this->having = $condition;
+		} else {
+			$this->having = array('and', $this->having, $condition);
+		}
+		$this->addParams($params);
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for adding a new DB column.
-	 * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
-	 * @param string $column the name of the new column. The name will be properly quoted by the method.
-	 * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
-	 * to convert the give column type to the physical one. For example, `string` will be converted
-	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+	 * Adds an additional HAVING condition to the existing one.
+	 * The new condition and the existing one will be joined using the 'OR' operator.
+	 * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+	 * on how to specify this parameter.
+	 * @param array $params the parameters (name=>value) to be bound to the query.
 	 * @return Query the query object itself
+	 * @see having()
+	 * @see andHaving()
 	 */
-	public function addColumn($table, $column, $type)
+	public function orHaving($condition, $params = array())
 	{
-		$this->operation = array(__FUNCTION__, $table, $column, $type);
+		if ($this->having === null) {
+			$this->having = $condition;
+		} else {
+			$this->having = array('or', $this->having, $condition);
+		}
+		$this->addParams($params);
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for dropping a DB column.
-	 * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
-	 * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+	 * Sets the ORDER BY part of the query.
+	 * @param string|array $columns the columns (and the directions) to be ordered by.
+	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
+	 * The method will automatically quote the column names unless a column contains some parenthesis
+	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
+	 * @see addOrder()
 	 */
-	public function dropColumn($table, $column)
+	public function orderBy($columns)
 	{
-		$this->operation = array(__FUNCTION__, $table, $column);
+		$this->orderBy = $columns;
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for renaming a column.
-	 * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
-	 * @param string $oldName the old name of the column. The name will be properly quoted by the method.
-	 * @param string $newName the new name of the column. The name will be properly quoted by the method.
+	 * Adds additional ORDER BY columns to the query.
+	 * @param string|array $columns the columns (and the directions) to be ordered by.
+	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
+	 * The method will automatically quote the column names unless a column contains some parenthesis
+	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
+	 * @see order()
 	 */
-	public function renameColumn($table, $oldName, $newName)
+	public function addOrderBy($columns)
 	{
-		$this->operation = array(__FUNCTION__, $table, $oldName, $newName);
+		if (empty($this->orderBy)) {
+			$this->orderBy = $columns;
+		} else {
+			if (!is_array($this->orderBy)) {
+				$this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY);
+			}
+			if (!is_array($columns)) {
+				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+			}
+			$this->orderBy = array_merge($this->orderBy, $columns);
+		}
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for changing the definition of a column.
-	 * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
-	 * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
-	 * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
-	 * to convert the give column type to the physical one. For example, `string` will be converted
-	 * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+	 * Sets the LIMIT part of the query.
+	 * @param integer $limit the limit
 	 * @return Query the query object itself
 	 */
-	public function alterColumn($table, $column, $type)
+	public function limit($limit)
 	{
-		$this->operation = array(__FUNCTION__, $table, $column, $type);
+		$this->limit = $limit;
 		return $this;
 	}
 
 	/**
-	 * Builds a SQL statement for adding a foreign key constraint to an existing table.
-	 * The method will properly quote the table and column names.
-	 * @param string $name the name of the foreign key constraint.
-	 * @param string $table the table that the foreign key constraint will be added to.
-	 * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
-	 * @param string $refTable the table that the foreign key references to.
-	 * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
-	 * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
-	 * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+	 * Sets the OFFSET part of the query.
+	 * @param integer $offset the offset
 	 * @return Query the query object itself
 	 */
-	public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+	public function offset($offset)
 	{
-		$this->operation = array(__FUNCTION__, $name, $table, $columns, $refTable, $refColumns, $delete, $update);
+		$this->offset = $offset;
 		return $this;
 	}
 
 	/**
-	 * Builds a SQL statement for dropping a foreign key constraint.
-	 * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
-	 * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+	 * Appends a SQL statement using UNION operator.
+	 * @param string|Query $sql the SQL statement to be appended using UNION
 	 * @return Query the query object itself
 	 */
-	public function dropForeignKey($name, $table)
+	public function union($sql)
 	{
-		$this->operation = array(__FUNCTION__, $name, $table);
+		$this->union[] = $sql;
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for creating a new index.
-	 * @param string $name the name of the index. The name will be properly quoted by the method.
-	 * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
-	 * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
-	 * by commas. The column names will be properly quoted by the method.
-	 * @param boolean $unique whether to add UNIQUE constraint on the created index.
+	 * Sets the parameters to be bound to the query.
+	 * @param array $params list of query parameter values indexed by parameter placeholders.
+	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
 	 * @return Query the query object itself
+	 * @see addParams()
 	 */
-	public function createIndex($name, $table, $columns, $unique = false)
+	public function params($params)
 	{
-		$this->operation = array(__FUNCTION__, $name, $table, $columns, $unique);
+		$this->params = $params;
 		return $this;
 	}
 
 	/**
-	 * Builds and executes a SQL statement for dropping an index.
-	 * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
-	 * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+	 * Adds additional parameters to be bound to the query.
+	 * @param array $params list of query parameter values indexed by parameter placeholders.
+	 * For example, `array(':name'=>'Dan', ':age'=>31)`.
 	 * @return Query the query object itself
+	 * @see params()
 	 */
-	public function dropIndex($name, $table)
+	public function addParams($params)
 	{
-		$this->operation = array(__FUNCTION__, $name, $table);
+		if ($params !== array()) {
+			if ($this->params === null) {
+				$this->params = $params;
+			} else {
+				foreach ($params as $name => $value) {
+					if (is_integer($name)) {
+						$this->params[] = $value;
+					} else {
+						$this->params[$name] = $value;
+					}
+				}
+			}
+		}
 		return $this;
 	}
 }
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index 3cfc874..d038076 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -12,7 +12,7 @@ namespace yii\db;
 use yii\db\Exception;
 
 /**
- * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[BaseQuery]] object.
+ * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
  *
  * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE,
  * from a [[Query]] object.
@@ -41,10 +41,6 @@ class QueryBuilder extends \yii\base\Object
 	 * Child classes should override this property to declare supported type mappings.
 	 */
 	public $typeMap = array();
-	/**
-	 * @var Query the Query object that is currently being processed by the query builder to generate a SQL statement.
-	 */
-	public $query;
 
 	/**
 	 * Constructor.
@@ -58,8 +54,8 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * Generates a SELECT SQL statement from a [[BaseQuery]] object.
-	 * @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated
+	 * Generates a SELECT SQL statement from a [[Query]] object.
+	 * @param Query $query the [[Query]] object from which the SQL statement will be generated
 	 * @return string the generated SQL statement
 	 */
 	public function build($query)
@@ -79,27 +75,29 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * Creates and executes an INSERT SQL statement.
-	 * The method will properly escape the column names, and bind the values to be inserted.
+	 * Creates an INSERT SQL statement.
 	 * For example,
 	 *
 	 * ~~~
 	 * $sql = $queryBuilder->insert('tbl_user', array(
 	 *	 'name' => 'Sam',
 	 *	 'age' => 30,
-	 * ));
+	 * ), $params);
 	 * ~~~
 	 *
+	 * The method will properly escape the table and column names.
+	 *
 	 * @param string $table the table that new rows will be inserted into.
 	 * @param array $columns the column data (name=>value) to be inserted into the table.
+	 * @param array $params the binding parameters that will be generated by this method.
+	 * They should be bound to the DB command later.
 	 * @return string the INSERT SQL
 	 */
-	public function insert($table, $columns)
+	public function insert($table, $columns, &$params)
 	{
 		$names = array();
 		$placeholders = array();
 		$count = 0;
-		$params = array();
 		foreach ($columns as $name => $value) {
 			$names[] = $this->quoteColumnName($name);
 			if ($value instanceof Expression) {
@@ -113,9 +111,6 @@ class QueryBuilder extends \yii\base\Object
 				$count++;
 			}
 		}
-		if ($this->query instanceof BaseQuery) {
-			$this->query->addParams($params);
-		}
 
 		return 'INSERT INTO ' . $this->quoteTableName($table)
 			. ' (' . implode(', ', $names) . ') VALUES ('
@@ -123,8 +118,7 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * Creates and executes an UPDATE SQL statement.
-	 * The method will properly escape the column names and bind the values to be updated.
+	 * Creates an UPDATE SQL statement.
 	 * For example,
 	 *
 	 * ~~~
@@ -134,14 +128,17 @@ class QueryBuilder extends \yii\base\Object
 	 * ), 'age > 30', $params);
 	 * ~~~
 	 *
+	 * The method will properly escape the table and column names.
+	 *
 	 * @param string $table the table to be updated.
 	 * @param array $columns the column data (name=>value) to be updated.
 	 * @param mixed $condition the condition that will be put in the WHERE part. Please
 	 * refer to [[Query::where()]] on how to specify condition.
-	 * @param array $params the parameters to be bound to the query.
+	 * @param array $params the binding parameters that will be modified by this method
+	 * so that they can be bound to the DB command later.
 	 * @return string the UPDATE SQL
 	 */
-	public function update($table, $columns, $condition = '', $params = array())
+	public function update($table, $columns, $condition = '', &$params)
 	{
 		$lines = array();
 		$count = 0;
@@ -157,9 +154,6 @@ class QueryBuilder extends \yii\base\Object
 				$count++;
 			}
 		}
-		if ($this->query instanceof BaseQuery) {
-			$this->query->addParams($params);
-		}
 		$sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines);
 		if (($where = $this->buildCondition($condition)) !== '') {
 			$sql .= ' WHERE ' . $where;
@@ -169,28 +163,26 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * Creates and executes a DELETE SQL statement.
+	 * Creates a DELETE SQL statement.
 	 * For example,
 	 *
 	 * ~~~
 	 * $sql = $queryBuilder->delete('tbl_user', 'status = 0');
 	 * ~~~
 	 *
+	 * The method will properly escape the table and column names.
+	 *
 	 * @param string $table the table where the data will be deleted from.
 	 * @param mixed $condition the condition that will be put in the WHERE part. Please
 	 * refer to [[Query::where()]] on how to specify condition.
-	 * @param array $params the parameters to be bound to the query.
 	 * @return string the DELETE SQL
 	 */
-	public function delete($table, $condition = '', $params = array())
+	public function delete($table, $condition = '')
 	{
 		$sql = 'DELETE FROM ' . $this->quoteTableName($table);
 		if (($where = $this->buildCondition($condition)) !== '') {
 			$sql .= ' WHERE ' . $where;
 		}
-		if ($params !== array() && $this->query instanceof BaseQuery) {
-			$this->query->addParams($params);
-		}
 		return $sql;
 	}
 
@@ -461,7 +453,7 @@ class QueryBuilder extends \yii\base\Object
 
 	/**
 	 * Parses the condition specification and generates the corresponding SQL expression.
-	 * @param string|array $condition the condition specification. Please refer to [[BaseQuery::where()]]
+	 * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
 	 * on how to specify a condition.
 	 * @return string the generated SQL expression
 	 * @throws \yii\db\Exception if the condition is in bad format
@@ -878,7 +870,7 @@ class QueryBuilder extends \yii\base\Object
 			$unions = array($unions);
 		}
 		foreach ($unions as $i => $union) {
-			if ($union instanceof BaseQuery) {
+			if ($union instanceof Query) {
 				$unions[$i] = $this->build($union);
 			}
 		}
diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php
index 7f62ee9..7f9ba1f 100644
--- a/framework/validators/ExistValidator.php
+++ b/framework/validators/ExistValidator.php
@@ -36,12 +36,6 @@ class ExistValidator extends Validator
 	 */
 	public $attributeName;
 	/**
-	 * @var \yii\db\BaseQuery additional query criteria. This will be combined
-	 * with the condition that checks if the attribute value exists in the
-	 * corresponding table column.
-	 */
-	public $query = null;
-	/**
 	 * @var boolean whether the attribute value can be null or empty. Defaults to true,
 	 * meaning that if the attribute is empty, it is considered valid.
 	 */
@@ -63,20 +57,17 @@ class ExistValidator extends Validator
 			return;
 		}
 
+		/** @var $className \yii\db\ActiveRecord */
 		$className = ($this->className === null) ? get_class($object) : \Yii::import($this->className);
 		$attributeName = ($this->attributeName === null) ? $attribute : $this->attributeName;
-		$table = $object::getMetaData()->table;
+		$table = $className::getTableSchema();
 		if (($column = $table->getColumn($attributeName)) === null) {
 			throw new \yii\base\Exception('Table "' . $table->name . '" does not have a column named "' . $attributeName . '"');
 		}
 
-		$finder = $object->find()->where(array($column->name => $value));
-
-		if ($this->query instanceof \yii\db\BaseQuery) {
-			$finder->mergeWith($this->query);
-		}
-
-		if (!$finder->exists()) {
+		$query = $className::find();
+		$query->where(array($column->name => $value));
+		if (!$query->exists()) {
 			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} "{value}" is invalid.');
 			$this->addError($object, $attribute, $message, array('{value}' => $value));
 		}
diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php
index df6147f..34f7e33 100644
--- a/framework/validators/UniqueValidator.php
+++ b/framework/validators/UniqueValidator.php
@@ -30,8 +30,8 @@ class UniqueValidator extends Validator
 	/**
 	 * @var string the yii\db\ActiveRecord class name or alias of the class
 	 * that should be used to look for the attribute value being validated.
-	 * Defaults to null, meaning using the class of the object currently
-	 * being validated.
+	 * Defaults to null, meaning using the yii\db\ActiveRecord class of
+	 * the attribute being validated.
 	 * @see attributeName
 	 */
 	public $className;
@@ -39,16 +39,9 @@ class UniqueValidator extends Validator
 	 * @var string the ActiveRecord class attribute name that should be
 	 * used to look for the attribute value being validated. Defaults to null,
 	 * meaning using the name of the attribute being validated.
-	 * @see className
 	 */
 	public $attributeName;
 	/**
-	 * @var \yii\db\ActiveQuery additional query criteria. This will be
-	 * combined with the condition that checks if the attribute value exists
-	 * in the corresponding table column.
-	 */
-	public $query = null;
-	/**
 	 * @var string the user-defined error message. The placeholders "{attribute}" and "{value}"
 	 * are recognized, which will be replaced with the actual attribute name and value, respectively.
 	 */
@@ -59,13 +52,11 @@ class UniqueValidator extends Validator
 	 */
 	public $skipOnError = true;
 
-
 	/**
 	 * Validates the attribute of the object.
 	 * If there is any error, the error message is added to the object.
-	 * @param \yiiunit\data\ar\ActiveRecord $object the object being validated
+	 * @param \yii\db\ActiveRecord $object the object being validated
 	 * @param string $attribute the attribute being validated
-	 *
 	 * @throws \yii\base\Exception if table doesn't have column specified
 	 */
 	public function validateAttribute($object, $attribute)
@@ -75,30 +66,27 @@ class UniqueValidator extends Validator
 			return;
 		}
 
+		/** @var $className \yii\db\ActiveRecord */
 		$className = ($this->className === null) ? get_class($object) : \Yii::import($this->className);
 		$attributeName = ($this->attributeName === null) ? $attribute : $this->attributeName;
 
-		$table = $object::getMetaData()->table;
+		$table = $className::getTableSchema();
 		if (($column = $table->getColumn($attributeName)) === null) {
 			throw new \yii\base\Exception('Table "' . $table->name . '" does not have a column named "' . $attributeName . '"');
 		}
 
-		$finder = $object::find();
-		$finder->where($this->caseSensitive ? "{$column->quotedName}=:value" : "LOWER({$column->quotedName})=LOWER(:value)");
-		$finder->params(array(':value' => $value));
-
-		if ($this->query instanceof \yii\db\BaseQuery) {
-			$finder->mergeWith($this->query);
-		}
+		$query = $className::find();
+		$query->where($this->caseSensitive ? "{$column->quotedName}=:value" : "LOWER({$column->quotedName})=LOWER(:value)");
+		$query->params(array(':value' => $value));
 
 		if ($object->getIsNewRecord()) {
 			// if current $object isn't in the database yet then it's OK just
 			// to call exists()
-			$exists = $finder->exists();
+			$exists = $query->exists();
 		} else {
 			// if current $object is in the database already we can't use exists()
-			$finder->limit(2);
-			$objects = $finder->all();
+			$query->limit(2);
+			$objects = $query->all();
 
 			$n = count($objects);
 			if ($n === 1) {
--
libgit2 0.27.1