From 2188ece107d4a813168b5fb14e643ee4e0590acd Mon Sep 17 00:00:00 2001 From: Qiang Xue 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 @@ - - * @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 * @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) {