Commit 965fe5c4 by Qiang Xue

...

parent cf10943c
<?php
/**
* ActiveFinder class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\ar;
use yii\base\VectorIterator;
use yii\db\dao\BaseQuery;
use yii\db\Exception;
/**
* ActiveFinder.php is ...
* todo: add SQL monitor
* todo: better handling on join() support in QueryBuilder: use regexp to detect table name and quote it
* todo: do not support anonymous parameter binding
* todo: add ActiveFinderBuilder
* todo: quote join/on part of the relational query
* todo: modify QueryBuilder about join() methods
* todo: unify ActiveFinder and ActiveRelation in query building process
* todo: intelligent table aliasing (first table name, then relation name, finally t?)
* todo: allow using tokens in primary query fragments
* todo: findBySql
* todo: base limited
* todo: lazy loading
* todo: scope
* todo: test via option
* todo: count, sum, exists
*
* @property integer $count
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveQuery extends BaseQuery implements \IteratorAggregate, \ArrayAccess, \Countable
{
/**
* @var string the name of the ActiveRecord class.
*/
public $modelClass;
/**
* @var array list of relations that this query should be performed with
*/
public $with;
/**
* @var string the table alias to be used for query
*/
public $tableAlias;
/**
* @var string the name of the column that the result should be indexed by.
* This is only useful when the query result is returned as an array.
*/
public $indexBy;
/**
* @var boolean whether to return each record as an array. If false (default), an object
* of [[modelClass]] will be created to represent each record.
*/
public $asArray;
/**
* @var array list of scopes that should be applied to this query
*/
public $scopes;
/**
* @var array list of query results
*/
public $records;
public $sql;
/**
* @param string $modelClass the name of the ActiveRecord class.
*/
public function __construct($modelClass)
{
$this->modelClass = $modelClass;
}
public function asArray($value = true)
{
$this->asArray = $value;
return $this;
}
public function with()
{
$this->with = func_get_args();
if (isset($this->with[0]) && is_array($this->with[0])) {
// the parameter is given as an array
$this->with = $this->with[0];
}
return $this;
}
public function indexBy($column)
{
$this->indexBy = $column;
return $this;
}
public function tableAlias($value)
{
$this->tableAlias = $value;
return $this;
}
/**
* Executes query and returns all results as an array.
* @return array the query results. If the query results in nothing, an empty array will be returned.
*/
public function all()
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
return $this->records;
}
/**
* Executes query and returns a single row of result.
* @return null|array|ActiveRecord the single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing.
*/
public function one()
{
if ($this->records === null) {
// todo: load only one record
$this->records = $this->findRecords();
}
return isset($this->records[0]) ? $this->records[0] : null;
}
public function value()
{
return 0;
}
public function exists()
{
return $this->select(array('1'))->asArray(true)->one() !== null;
}
/**
* Returns the database connection used by this query.
* This method returns the connection used by the [[modelClass]].
* @return \yii\db\dao\Connection the database connection used by this query
*/
public function getDbConnection()
{
$class = $this->modelClass;
return $class::getDbConnection();
}
/**
* Returns the number of items in the vector.
* @return integer the number of items in the vector
*/
public function getCount()
{
return $this->count();
}
/**
* Sets the parameters about query caching.
* This is a shortcut method to {@link CDbConnection::cache()}.
* It changes the query caching parameter of the {@link dbConnection} instance.
* @param integer $duration the number of seconds that query results may remain valid in cache.
* If this is 0, the caching will be disabled.
* @param CCacheDependency $dependency the dependency that will be used when saving the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
* @return ActiveRecord the active record instance itself.
*/
public function cache($duration, $dependency = null, $queryCount = 1)
{
$this->getDbConnection()->cache($duration, $dependency, $queryCount);
return $this;
}
/**
* Returns an iterator for traversing the items in the vector.
* This method is required by the SPL interface `IteratorAggregate`.
* It will be implicitly called when you use `foreach` to traverse the vector.
* @return VectorIterator an iterator for traversing the items in the vector.
*/
public function getIterator()
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
return new VectorIterator($this->records);
}
/**
* Returns the number of items in the vector.
* This method is required by the SPL `Countable` interface.
* It will be implicitly called when you use `count($vector)`.
* @return integer number of items in the vector.
*/
public function count()
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
return count($this->records);
}
/**
* Returns a value indicating whether there is an item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `isset($vector[$offset])`.
* @param integer $offset the offset to be checked
* @return boolean whether there is an item at the specified offset.
*/
public function offsetExists($offset)
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
return isset($this->records[$offset]);
}
/**
* Returns the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `$value = $vector[$offset];`.
* This is equivalent to [[itemAt]].
* @param integer $offset the offset to retrieve item.
* @return ActiveRecord the item at the offset
* @throws Exception if the offset is out of range
*/
public function offsetGet($offset)
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
return isset($this->records[$offset]) ? $this->records[$offset] : null;
}
/**
* Sets the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `$vector[$offset] = $item;`.
* If the offset is null or equal to the number of the existing items,
* the new item will be appended to the vector.
* Otherwise, the existing item at the offset will be replaced with the new item.
* @param integer $offset the offset to set item
* @param ActiveRecord $item the item value
* @throws Exception if the offset is out of range, or the vector is read only.
*/
public function offsetSet($offset, $item)
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
$this->records[$offset] = $item;
}
/**
* Unsets the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `unset($vector[$offset])`.
* This is equivalent to [[removeAt]].
* @param integer $offset the offset to unset item
* @throws Exception if the offset is out of range, or the vector is read only.
*/
public function offsetUnset($offset)
{
if ($this->records === null) {
$this->records = $this->findRecords();
}
unset($this->records[$offset]);
}
public function joinWith()
{
// todo: inner join with one or multiple relations as filters
}
protected function findRecords()
{
if (!empty($this->with)) {
// todo: handle findBySql() and limit cases
$joinTree = $this->buildRelationalQuery();
}
if ($this->sql === null) {
$this->initFrom($this->query);
$command = $this->query->createCommand($this->getDbConnection());
$this->sql = $command->getSql();
} else {
$command = $this->getDbConnection()->createCommand($this->sql);
$command->bindValues($this->query->params);
}
$rows = $command->queryAll();
if (isset($joinTree)) {
foreach ($rows as $row) {
$joinTree->populateData($row);
}
return array_values($joinTree->records);
}
if ($this->asArray) {
if ($this->indexBy === null) {
return $rows;
}
$records = array();
foreach ($rows as $row) {
$records[$row[$this->indexBy]] = $row;
}
return $records;
} else {
$records = array();
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$records[] = $class::populateData($row);
}
} else {
$attribute = $this->indexBy;
foreach ($rows as $row) {
$record = $class::populateData($row);
$records[$record->$attribute] = $record;
}
}
return $records;
}
}
protected function initFrom($query)
{
if ($query->from === null) {
$modelClass = $this->modelClass;
$tableName = $modelClass::tableName();
if ($this->tableAlias !== null) {
$tableName .= ' ' . $this->tableAlias;
}
$query->from = array($tableName);
}
}
protected function buildRelationalQuery()
{
$joinTree = new JoinElement($this, null, null);
$this->buildJoinTree($joinTree, $this->with);
$this->buildTableAlias($joinTree);
$query = new Query;
foreach ($joinTree->children as $child) {
$child->buildQuery($query);
}
$select = $joinTree->buildSelect($this->query->select);
if (!empty($query->select)) {
$this->query->select = array_merge($select, $query->select);
} else {
$this->query->select = $select;
}
if (!empty($query->where)) {
$this->query->andWhere('(' . implode(') AND (', $query->where) . ')');
}
if (!empty($query->having)) {
$this->query->andHaving('(' . implode(') AND (', $query->having) . ')');
}
if (!empty($query->join)) {
if ($this->query->join === null) {
$this->query->join = $query->join;
} else {
$this->query->join = array_merge($this->query->join, $query->join);
}
}
if (!empty($query->orderBy)) {
$this->query->addOrderBy($query->orderBy);
}
if (!empty($query->groupBy)) {
$this->query->addGroupBy($query->groupBy);
}
if (!empty($query->params)) {
$this->query->addParams($query->params);
}
return $joinTree;
}
/**
* @param JoinElement $parent
* @param array|string $with
* @param array $config
* @return null|JoinElement
* @throws \yii\db\Exception
*/
protected function buildJoinTree($parent, $with, $config = array())
{
if (is_array($with)) {
foreach ($with as $name => $value) {
if (is_string($value)) {
$this->buildJoinTree($parent, $value);
} elseif (is_string($name) && is_array($value)) {
$this->buildJoinTree($parent, $name, $value);
}
}
return null;
}
if (($pos = strrpos($with, '.')) !== false) {
$parent = $this->buildJoinTree($parent, substr($with, 0, $pos));
$with = substr($with, $pos + 1);
}
if (isset($parent->children[$with])) {
$child = $parent->children[$with];
$child->joinOnly = false;
} else {
$modelClass = $parent->relation->modelClass;
$relations = $modelClass::getMetaData()->relations;
if (!isset($relations[$with])) {
throw new Exception("$modelClass has no relation named '$with'.");
}
$relation = clone $relations[$with];
if ($relation->via !== null && isset($relations[$relation->via])) {
$relation->via = null;
$parent2 = $this->buildJoinTree($parent, $relation->via);
if ($parent2->joinOnly === null) {
$parent2->joinOnly = true;
}
$child = new JoinElement($relation, $parent2, $parent);
} else {
$child = new JoinElement($relation, $parent, $parent);
}
}
foreach ($config as $name => $value) {
$child->relation->$name = $value;
}
return $child;
}
protected function buildTableAlias($element, &$count = 0)
{
if ($element->relation->tableAlias === null) {
$element->relation->tableAlias = 't' . ($count++);
}
foreach ($element->children as $child) {
$this->buildTableAlias($child, $count);
}
}
}
...@@ -38,6 +38,10 @@ abstract class ActiveRecord extends Model ...@@ -38,6 +38,10 @@ abstract class ActiveRecord extends Model
* @var array old attribute values indexed by attribute names. * @var array old attribute values indexed by attribute names.
*/ */
private $_oldAttributes; private $_oldAttributes;
/**
* @var array related records indexed by relation names.
*/
private $_related;
/** /**
* Returns the metadata for this AR class. * Returns the metadata for this AR class.
...@@ -61,104 +65,167 @@ abstract class ActiveRecord extends Model ...@@ -61,104 +65,167 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Creates an [[ActiveFinder]] instance for query purpose. * Creates an [[ActiveQuery]] instance for query purpose.
* *
* Because [[ActiveFinder]] implements a set of query building methods, * Because [[ActiveQuery]] implements a set of query building methods,
* additional query conditions can be specified by calling these methods. * additional query conditions can be specified by calling the methods of [[ActiveQuery]].
* *
* Below are some usage examples: * Below are some usage examples:
* *
* ~~~ * ~~~
* // find all customers * // find all customers
* $customers = Customer::find()->all(); * $customers = Customer::find()->all();
* // find a single customer whose ID is 10 * // find a single customer whose primary key value is 10
* $customer = Customer::find(10)->one(); * $customer = Customer::find(10)->one();
* // find all active customers and order them by their age: * // find all active customers and order them by their age:
* $customers = Customer::find(array('status' => 1))->orderBy('age')->all(); * $customers = Customer::find()
* ->where(array('status' => 1))
* ->orderBy('age')
* ->all();
* // or alternatively:
* $customers = Customer::find(array(
* 'where' => array('status' => 1),
* 'orderBy' => 'age',
* ))->all();
* ~~~ * ~~~
* *
* @param mixed $q the query parameter. This can be one of the followings: * @param mixed $q the query parameter. This can be one of the followings:
* *
* - a scalar value (integer, string): query by a single primary key value. * - a scalar value (integer or string): query by a single primary key value.
* - an array of name-value pairs: query by a set of column values. * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object.
* - a [[Query]] object: query by a full query object.
* *
* @return ActiveFinder the [[ActiveFinder]] instance for query purpose. * @return ActiveQuery the [[ActiveQuery]] instance for query purpose.
* @throws Exception if the query parameter is invalid.
*/ */
public static function find($q = null) public static function find($q = null)
{ {
$finder = static::createActiveFinder(); $query = static::createActiveQuery();
if ($q instanceof Query) { if (is_array($q)) {
$finder->query = $q; foreach ($q as $name => $value) {
} elseif (is_array($q)) { $query->$name = $value;
// query by a set of column values }
$finder->where($q);
} elseif ($q !== null) { } elseif ($q !== null) {
// query by primary key // query by primary key
$primaryKey = static::getMetaData()->table->primaryKey; $primaryKey = static::getMetaData()->table->primaryKey;
if (count($primaryKey) === 1) { $query->where(array($primaryKey[0] => $q));
$finder->where(array($primaryKey[0] => $q));
} else {
throw new Exception('Multiple values are required to query by composite primary keys.');
}
} }
return $finder; return $query;
} }
/** /**
* Creates an [[ActiveFinder]] instance and query by a given SQL statement. * Creates an [[ActiveQuery]] instance and query by a given SQL statement.
* Note that because the SQL statement is already specified, calling further * Note that because the SQL statement is already specified, calling further
* query methods (such as `where()`, `orderBy()`) on [[ActiveFinder]] will have no effect. * query methods (such as `where()`, `orderBy()`) on [[ActiveQuery]] will have no effect.
* Methods such as `with()`, `asArray()` can still be called though.
* @param string $sql the SQL statement to be executed * @param string $sql the SQL statement to be executed
* @param array $params parameters to be bound to the SQL statement during execution. * @param array $params parameters to be bound to the SQL statement during execution.
* @return ActiveFinder the [[ActiveFinder]] instance * @return ActiveQuery the [[ActiveQuery]] instance
*/ */
public static function findBySql($sql, $params = array()) public static function findBySql($sql, $params = array())
{ {
$finder = static::createActiveFinder(); $query = static::createActiveQuery();
$finder->sql = $sql; $query->sql = $sql;
return $finder->params($params); return $query->params($params);
}
/**
* Performs a COUNT query for this AR class.
*
* Below are some usage examples:
*
* ~~~
* // count the total number of customers
* echo Customer::count();
* // count the number of customers whose primary key value is 10.
* echo Customer::count(10);
* // count the number of active customers:
* echo Customer::count(array(
* 'where' => array('status' => 1),
* ));
* ~~~
*
* @param mixed $q the query parameter. This can be one of the followings:
*
* - a scalar value (integer or string): query by a single primary key value.
* - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object for query purpose.
*
* @return integer the counting result
*/
public static function count($q = null)
{
$query = static::createActiveQuery();
if (is_array($q)) {
foreach ($q as $name => $value) {
$query->$name = $value;
}
} elseif ($q !== null) {
// query by primary key
$primaryKey = static::getMetaData()->table->primaryKey;
$query->where(array($primaryKey[0] => $q));
}
if ($query->select === null) {
$query->select = 'COUNT(*)';
}
return $query->value();
} }
/**
* Updates the whole table using the provided attribute values and conditions.
* @param array $attributes attribute values to be saved into the table
* @param string|array $condition the conditions that will be put in the WHERE part.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* @return integer the number of rows updated
*/
public static function updateAll($attributes, $condition = '', $params = array()) public static function updateAll($attributes, $condition = '', $params = array())
{ {
$class = get_called_class();
$query = new Query; $query = new Query;
$query->update($class::tableName(), $attributes, $condition, $params); $query->update(static::tableName(), $attributes, $condition, $params);
return $query->createCommand($class::getDbConnection())->execute(); return $query->createCommand(static::getDbConnection())->execute();
} }
public static function updateCounters($counters, $condition = '', $params = array()) /**
* Updates the whole table using the provided counter values and conditions.
* @param array $counters the counters to be updated (attribute name => increment value).
* @param string|array $condition the conditions that will be put in the WHERE part.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* @return integer the number of rows updated
*/
public static function updateAllCounters($counters, $condition = '', $params = array())
{ {
$class = get_called_class(); $db = static::getDbConnection();
$db = $class::getDbConnection();
foreach ($counters as $name => $value) { foreach ($counters as $name => $value) {
$value = (int)$value; $value = (int)$value;
$quotedName = $db->quoteColumnName($name, true); $quotedName = $db->quoteColumnName($name, true);
$counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value"); $counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value");
} }
$query = new Query; $query = new Query;
$query->update($class::tableName(), $counters, $condition, $params); $query->update(static::tableName(), $counters, $condition, $params);
return $query->createCommand($class::getDbConnection())->execute(); return $query->createCommand($db)->execute();
} }
/**
* Deletes rows in the table using the provided conditions.
* @param string|array $condition the conditions that will be put in the WHERE part.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* @return integer the number of rows updated
*/
public static function deleteAll($condition = '', $params = array()) public static function deleteAll($condition = '', $params = array())
{ {
$class = get_called_class();
$query = new Query; $query = new Query;
$query->delete($class::tableName(), $condition, $params); $query->delete(static::tableName(), $condition, $params);
return $query->createCommand($class::getDbConnection())->execute(); return $query->createCommand(static::getDbConnection())->execute();
} }
/** /**
* Creates a [[ActiveFinder]] instance. * Creates a [[ActiveQuery]] instance.
* This method is mainly called by [[find()]] and [[findBySql()]]. * This method is called by [[find()]] and [[findBySql()]] to start a SELECT query.
* @return ActiveFinder the newly created [[ActiveFinder]] instance. * @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/ */
public static function createActiveFinder() public static function createActiveQuery()
{ {
return new ActiveFinder(get_called_class()); return new ActiveQuery(get_called_class());
} }
/** /**
...@@ -175,8 +242,8 @@ abstract class ActiveRecord extends Model ...@@ -175,8 +242,8 @@ abstract class ActiveRecord extends Model
/** /**
* Declares the primary key name for this AR class. * Declares the primary key name for this AR class.
* This method is meant to be overridden in case when the table is not defined with a primary key * This method is meant to be overridden in case when the table has no primary key defined
* (for some legacy database). If the table is already defined with a primary key, * (for some legacy database). If the table already has a primary key,
* you do not need to override this method. The default implementation simply returns null, * you do not need to override this method. The default implementation simply returns null,
* meaning using the primary key defined in the database table. * meaning using the primary key defined in the database table.
* @return string|array the primary key of the associated database table. * @return string|array the primary key of the associated database table.
...@@ -193,7 +260,7 @@ abstract class ActiveRecord extends Model ...@@ -193,7 +260,7 @@ abstract class ActiveRecord extends Model
* *
* Child classes may override this method to specify their relations. * Child classes may override this method to specify their relations.
* *
* The following shows how to declare relations for a `Programmer` AR class: * The following code shows how to declare relations for a `Programmer` AR class:
* *
* ~~~ * ~~~
* return array( * return array(
...@@ -350,14 +417,17 @@ abstract class ActiveRecord extends Model ...@@ -350,14 +417,17 @@ abstract class ActiveRecord extends Model
*/ */
public function __get($name) public function __get($name)
{ {
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { if (isset($this->_attributes[$name])) {
return $this->_attributes[$name]; return $this->_attributes[$name];
} else { }
$md = $this->getMetaData(); $md = $this->getMetaData();
if (isset($md->table->columns[$name])) { if (isset($md->table->columns[$name])) {
return null; return null;
} elseif (isset($md->relations[$name])) { } elseif (isset($md->relations[$name])) {
return $this->_attributes[$name] = $this->loadRelatedRecord($md->relations[$name]); if (array_key_exists($name, $this->_related)) {
return $this->_related[$name];
} else {
return $this->_related[$name] = $this->loadRelatedRecord($md->relations[$name]);
} }
} }
return parent::__get($name); return parent::__get($name);
...@@ -372,8 +442,10 @@ abstract class ActiveRecord extends Model ...@@ -372,8 +442,10 @@ abstract class ActiveRecord extends Model
public function __set($name, $value) public function __set($name, $value)
{ {
$md = $this->getMetaData(); $md = $this->getMetaData();
if (isset($md->table->columns[$name]) || isset($md->relations[$name])) { if (isset($md->table->columns[$name])) {
$this->_attributes[$name] = $value; $this->_attributes[$name] = $value;
} elseif (isset($md->relations[$name])) {
$this->_related[$name] = $value;
} else { } else {
parent::__set($name, $value); parent::__set($name, $value);
} }
...@@ -388,9 +460,11 @@ abstract class ActiveRecord extends Model ...@@ -388,9 +460,11 @@ abstract class ActiveRecord extends Model
*/ */
public function __isset($name) public function __isset($name)
{ {
if (isset($this->_attributes[$name])) { if (isset($this->_attributes[$name]) || isset($this->_related[$name])) {
return true; return true;
} elseif (isset($this->getMetaData()->table->columns[$name]) || isset($this->getMetaData()->relations[$name])) { }
$md = $this->getMetaData();
if (isset($md->table->columns[$name]) || isset($md->relations[$name])) {
return false; return false;
} else { } else {
return parent::__isset($name); return parent::__isset($name);
...@@ -406,8 +480,10 @@ abstract class ActiveRecord extends Model ...@@ -406,8 +480,10 @@ abstract class ActiveRecord extends Model
public function __unset($name) public function __unset($name)
{ {
$md = $this->getMetaData(); $md = $this->getMetaData();
if (isset($md->table->columns[$name]) || isset($md->relations[$name])) { if (isset($md->table->columns[$name])) {
unset($this->_attributes[$name]); unset($this->_attributes[$name]);
} elseif (isset($md->relations[$name])) {
unset($this->_related[$name]);
} else { } else {
parent::__unset($name); parent::__unset($name);
} }
...@@ -432,20 +508,20 @@ abstract class ActiveRecord extends Model ...@@ -432,20 +508,20 @@ abstract class ActiveRecord extends Model
/** /**
* Initializes the internal storage for the relation. * Initializes the internal storage for the relation.
* This method is internally used by [[ActiveFinder]] when populating relation data. * This method is internally used by [[ActiveQuery]] when populating relation data.
* @param ActiveRelation $relation the relation object * @param ActiveRelation $relation the relation object
*/ */
public function initRelation($relation) public function initRelation($relation)
{ {
$this->_attributes[$relation->name] = $relation->hasMany ? array() : null; $this->_related[$relation->name] = $relation->hasMany ? array() : null;
} }
public function addRelatedRecord($relation, $record) public function addRelatedRecord($relation, $record)
{ {
if ($relation->hasMany) { if ($relation->hasMany) {
$this->_attributes[$relation->name][] = $record; $this->_related[$relation->name][] = $record;
} else { } else {
$this->_attributes[$relation->name] = $record; $this->_related[$relation->name] = $record;
} }
} }
...@@ -472,7 +548,7 @@ abstract class ActiveRecord extends Model ...@@ -472,7 +548,7 @@ abstract class ActiveRecord extends Model
} }
$relation = $md->relations[$relation]; $relation = $md->relations[$relation];
} }
$finder = $this->createActiveFinder(); $finder = $this->createActiveQuery();
} }
/** /**
...@@ -541,7 +617,7 @@ abstract class ActiveRecord extends Model ...@@ -541,7 +617,7 @@ abstract class ActiveRecord extends Model
} }
$names = array_flip($names); $names = array_flip($names);
$attributes = array(); $attributes = array();
if (empty($this->_oldAttributes)) { if ($this->_oldAttributes === null) {
foreach ($this->_attributes as $name => $value) { foreach ($this->_attributes as $name => $value) {
if (isset($names[$name])) { if (isset($names[$name])) {
$attributes[$name] = $value; $attributes[$name] = $value;
...@@ -584,6 +660,124 @@ abstract class ActiveRecord extends Model ...@@ -584,6 +660,124 @@ abstract class ActiveRecord extends Model
{ {
if (!$runValidation || $this->validate($attributes)) { if (!$runValidation || $this->validate($attributes)) {
return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes); return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
}
return false;
}
/**
* Inserts a row into the table based on this active record attributes.
* If the table's primary key is auto-incremental and is null before insertion,
* it will be populated with the actual value after insertion.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
* and its {@link scenario} property will be set to be 'update'.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws Exception if the record is not new
*/
public function insert($attributes = null)
{
if ($this->beforeInsert()) {
$query = new Query;
$values = $this->getChangedAttributes($attributes);
$db = $this->getDbConnection();
$command = $query->insert($this->tableName(), $values)->createCommand($db);
if ($command->execute()) {
$table = $this->getMetaData()->table;
if ($table->sequenceName !== null) {
foreach ($table->primaryKey as $name) {
if (!isset($this->_attributes[$name])) {
$this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
break;
}
}
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value;
}
$this->afterInsert();
return true;
}
}
return false;
}
/**
* Updates the row represented by this active record.
* All loaded attributes will be saved to the database.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the update is successful
* @throws Exception if the record is new
*/
public function update($attributes = null)
{
if ($this->getIsNewRecord()) {
throw new Exception('The active record cannot be updated because it is new.');
}
if ($this->beforeUpdate()) {
$values = $this->getChangedAttributes($attributes);
if ($values !== array()) {
$this->updateAll($values, $this->getOldPrimaryKey(true));
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterUpdate();
}
return true;
} else {
return false;
}
}
/**
* Saves one or several counter columns for the current AR object.
* Note that this method differs from [[updateAllCounters()]] in that it only
* saves counters for the current AR object.
*
* An example usage is as follows:
*
* ~~~
* $post = Post::find($id)->one();
* $post->updateCounters(array('view_count' => 1));
* ~~~
*
* Use negative values if you want to decrease the counters.
* @param array $counters the counters to be updated (attribute name => increment value)
* @return boolean whether the saving is successful
* @throws Exception if the record is new or any database error
* @see updateAllCounters()
*/
public function updateCounters($counters)
{
if ($this->getIsNewRecord()) {
throw new Exception('The active record cannot be updated because it is new.');
}
$this->updateAllCounters($counters, $this->getOldPrimaryKey(true));
foreach ($counters as $name => $value) {
$this->_attributes[$name] += $value;
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
return true;
}
/**
* Deletes the row corresponding to this active record.
* @return boolean whether the deletion is successful.
* @throws Exception if the record is new or any database error
*/
public function delete()
{
if ($this->getIsNewRecord()) {
throw new Exception('The active record cannot be deleted because it is new.');
}
if ($this->beforeDelete()) {
$result = $this->deleteAll($this->getPrimaryKey(true)) > 0;
$this->_oldAttributes = null;
$this->afterDelete();
return $result;
} else { } else {
return false; return false;
} }
...@@ -598,7 +792,7 @@ abstract class ActiveRecord extends Model ...@@ -598,7 +792,7 @@ abstract class ActiveRecord extends Model
*/ */
public function getIsNewRecord() public function getIsNewRecord()
{ {
return empty($this->_oldAttributes); return $this->_oldAttributes === null;
} }
/** /**
...@@ -608,41 +802,51 @@ abstract class ActiveRecord extends Model ...@@ -608,41 +802,51 @@ abstract class ActiveRecord extends Model
*/ */
public function setIsNewRecord($value) public function setIsNewRecord($value)
{ {
if ($value) { $this->_oldAttributes = $value ? null : $this->_attributes;
$this->_oldAttributes = null;
} else {
$this->_oldAttributes = array();
foreach ($this->attributeNames() as $name) {
if (isset($this->_attributes[$name])) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
} }
/**
* This event is raised before the record is saved.
* By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
* @param \yii\base\ModelEvent $event the event parameter
*/
public function onBeforeInsert($event)
{
$this->raiseEvent('onBeforeInsert', $event);
} }
/**
* This event is raised after the record is saved.
* @param \yii\base\Event $event the event parameter
*/
public function onAfterInsert($event)
{
$this->raiseEvent('onAfterInsert', $event);
} }
/** /**
* This event is raised before the record is saved. * This event is raised before the record is saved.
* By setting {@link ModelEvent::isValid} to be false, the normal {@link save()} process will be stopped. * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
* @param ModelEvent $event the event parameter * @param \yii\base\ModelEvent $event the event parameter
*/ */
public function onBeforeSave($event) public function onBeforeUpdate($event)
{ {
$this->raiseEvent('onBeforeSave', $event); $this->raiseEvent('onBeforeUpdate', $event);
} }
/** /**
* This event is raised after the record is saved. * This event is raised after the record is saved.
* @param Event $event the event parameter * @param \yii\base\Event $event the event parameter
*/ */
public function onAfterSave($event) public function onAfterUpdate($event)
{ {
$this->raiseEvent('onAfterSave', $event); $this->raiseEvent('onAfterUpdate', $event);
} }
/** /**
* This event is raised before the record is deleted. * This event is raised before the record is deleted.
* By setting {@link ModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped. * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped.
* @param ModelEvent $event the event parameter * @param \yii\base\ModelEvent $event the event parameter
*/ */
public function onBeforeDelete($event) public function onBeforeDelete($event)
{ {
...@@ -651,7 +855,7 @@ abstract class ActiveRecord extends Model ...@@ -651,7 +855,7 @@ abstract class ActiveRecord extends Model
/** /**
* This event is raised after the record is deleted. * This event is raised after the record is deleted.
* @param Event $event the event parameter * @param \yii\base\Event $event the event parameter
*/ */
public function onAfterDelete($event) public function onAfterDelete($event)
{ {
...@@ -667,10 +871,37 @@ abstract class ActiveRecord extends Model ...@@ -667,10 +871,37 @@ abstract class ActiveRecord extends Model
* Make sure you call the parent implementation so that the event is raised properly. * Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the saving should be executed. Defaults to true. * @return boolean whether the saving should be executed. Defaults to true.
*/ */
public function beforeSave() public function beforeInsert()
{
$event = new ModelEvent($this);
$this->onBeforeInsert($event);
return $event->isValid;
}
/**
* This method is invoked after saving a record successfully.
* The default implementation raises the {@link onAfterSave} event.
* You may override this method to do postprocessing after record saving.
* Make sure you call the parent implementation so that the event is raised properly.
*/
public function afterInsert()
{
$this->onAfterInsert(new Event($this));
}
/**
* This method is invoked before saving a record (after validation, if any).
* The default implementation raises the {@link onBeforeSave} event.
* You may override this method to do any preparation work for record saving.
* Use {@link isNewRecord} to determine whether the saving is
* for inserting or updating record.
* Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the saving should be executed. Defaults to true.
*/
public function beforeUpdate()
{ {
$event = new ModelEvent($this); $event = new ModelEvent($this);
$this->onBeforeSave($event); $this->onBeforeUpdate($event);
return $event->isValid; return $event->isValid;
} }
...@@ -680,9 +911,9 @@ abstract class ActiveRecord extends Model ...@@ -680,9 +911,9 @@ abstract class ActiveRecord extends Model
* You may override this method to do postprocessing after record saving. * You may override this method to do postprocessing after record saving.
* Make sure you call the parent implementation so that the event is raised properly. * Make sure you call the parent implementation so that the event is raised properly.
*/ */
public function afterSave() public function afterUpdate()
{ {
$this->onAfterSave(new Event($this)); $this->onAfterUpdate(new Event($this));
} }
/** /**
...@@ -711,183 +942,31 @@ abstract class ActiveRecord extends Model ...@@ -711,183 +942,31 @@ abstract class ActiveRecord extends Model
} }
/** /**
* Inserts a row into the table based on this active record attributes.
* If the table's primary key is auto-incremental and is null before insertion,
* it will be populated with the actual value after insertion.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
* and its {@link scenario} property will be set to be 'update'.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws Exception if the record is not new
*/
public function insert($attributes = null)
{
if ($this->beforeSave()) {
$db = $this->getDbConnection();
$query = new Query;
$values = $this->getChangedAttributes($attributes);
$command = $query->insert($this->tableName(), $values)->createCommand($db);
if ($command->execute()) {
$table = $this->getMetaData()->table;
if ($table->sequenceName !== null) {
foreach ($table->primaryKey as $name) {
if ($this->$name === null) {
$this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
break;
}
}
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterSave();
$this->setIsNewRecord(false);
return true;
}
}
return false;
}
/**
* Updates the row represented by this active record.
* All loaded attributes will be saved to the database.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the update is successful
* @throws Exception if the record is new
*/
public function update($attributes = null)
{
if ($this->beforeSave()) {
$values = $this->getChangedAttributes($attributes);
if ($values !== array()) {
$this->updateAll($values, $this->getOldPrimaryKey(true));
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
}
$this->afterSave();
$this->setIsNewRecord(false);
return true;
} else {
return false;
}
}
/**
* Saves a selected list of attributes.
* Unlike {@link save}, this method only saves the specified attributes
* of an existing row and does NOT call either {@link beforeSave} or {@link afterSave}.
* Also note that this method does not validate attributes.
* So do not use this method with untrusted data (such as user posted data).
* You may consider the following alternative if you want to do so:
*
* ~~~
* $user = User::find($id)->one;
* $user->attributes = $_POST['User'];
* $user->save();
* ~~~
*
* @param array $attributes attributes to be updated. Each element represents an attribute name
* or an attribute value indexed by its name. If the latter, the record's
* attribute will be changed accordingly before saving.
* @return boolean whether the update is successful
* @throws Exception if the record is new or any database error
*/
public function saveAttributes($attributes)
{
if (!$this->getIsNewRecord()) {
$values = array();
foreach ($attributes as $name => $value) {
if (is_integer($name)) {
$values[$value] = $this->$value;
} else {
$values[$name] = $this->$name = $value;
}
}
$this->updateAll($values, $this->getOldPrimaryKey(true));
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $value;
}
return true;
} else {
throw new Exception('The active record cannot be updated because it is new.');
}
}
/**
* Saves one or several counter columns for the current AR object.
* Note that this method differs from {@link updateCounters} in that it only
* saves the current AR object.
* An example usage is as follows:
* <pre>
* $postRecord=Post::model()->findByPk($postID);
* $postRecord->saveCounters(array('view_count'=>1));
* </pre>
* Use negative values if you want to decrease the counters.
* @param array $counters the counters to be updated (column name=>increment value)
* @return boolean whether the saving is successful
* @see updateCounters
*/
public function saveCounters($counters)
{
if (!$this->getIsNewRecord()) {
$this->updateCounters($counters, $this->getOldPrimaryKey(true));
foreach ($counters as $name => $value) {
$this->$name += $value;
$this->_oldAttributes[$name] = $this->$name;
}
return true;
} else {
throw new Exception('The active record cannot be updated because it is new.');
}
}
/**
* Deletes the row corresponding to this active record.
* @return boolean whether the deletion is successful.
* @throws Exception if the record is new
*/
public function delete()
{
if (!$this->getIsNewRecord()) {
if ($this->beforeDelete()) {
$result = $this->deleteAll($this->getPrimaryKey(true)) > 0;
$this->_oldAttributes = null;
$this->afterDelete();
return $result;
} else {
return false;
}
} else {
throw new Exception('The active record cannot be deleted because it is new.');
}
}
/**
* Repopulates this active record with the latest data. * Repopulates this active record with the latest data.
* @param array $attributes * @param array $attributes
* @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record. * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
*/ */
public function refresh($attributes = null) public function refresh($attributes = null)
{ {
if (!$this->getIsNewRecord() && ($record = $this->find($this->getPrimaryKey(true))) !== null) { if ($this->getIsNewRecord()) {
if ($attributes === null) { return false;
$attributes = $this->attributeNames();
} }
$this->_attributes = array(); $record = $this->find()->where($this->getPrimaryKey(true))->one();
foreach ($attributes as $name) { if ($record === null) {
return false;
}
if ($attributes === null) {
foreach ($this->attributeNames() as $name) {
$this->_attributes[$name] = $record->_attributes[$name]; $this->_attributes[$name] = $record->_attributes[$name];
} }
$this->_oldAttributes = $this->_attributes; $this->_oldAttributes = $this->_attributes;
return true;
} else { } else {
return false; foreach ($attributes as $name) {
$this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name];
} }
} }
return true;
}
/** /**
* Compares current active record with another one. * Compares current active record with another one.
...@@ -911,11 +990,11 @@ abstract class ActiveRecord extends Model ...@@ -911,11 +990,11 @@ abstract class ActiveRecord extends Model
{ {
$table = static::getMetaData()->table; $table = static::getMetaData()->table;
if (count($table->primaryKey) === 1 && !$asArray) { if (count($table->primaryKey) === 1 && !$asArray) {
return $this->{$table->primaryKey[0]}; return isset($this->_attributes[$table->primaryKey[0]]) ? $this->_attributes[$table->primaryKey[0]] : null;
} else { } else {
$values = array(); $values = array();
foreach ($table->primaryKey as $name) { foreach ($table->primaryKey as $name) {
$values[$name] = $this->$name; $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
} }
return $values; return $values;
} }
...@@ -928,7 +1007,8 @@ abstract class ActiveRecord extends Model ...@@ -928,7 +1007,8 @@ abstract class ActiveRecord extends Model
* The value remains unchanged even if the primary key attribute is manually assigned with a different value. * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
* @param boolean $asArray whether to return the primary key value as an array. If true, * @param boolean $asArray whether to return the primary key value as an array. If true,
* the return value will be an array with column name as key and column value as value. * the return value will be an array with column name as key and column value as value.
* @return mixed the old primary key value. An array (column name=>column value) is returned if the primary key is composite. * If this is false (default), a scalar value will be returned for non-composite primary key.
* @return string|array the old primary key value. An array (column name=>column value) is returned if the primary key is composite.
* If primary key is not defined, null will be returned. * If primary key is not defined, null will be returned.
*/ */
public function getOldPrimaryKey($asArray = false) public function getOldPrimaryKey($asArray = false)
...@@ -948,19 +1028,19 @@ abstract class ActiveRecord extends Model ...@@ -948,19 +1028,19 @@ abstract class ActiveRecord extends Model
/** /**
* Creates an active record with the given attributes. * Creates an active record with the given attributes.
* This method is internally used by the find methods. * This method is internally used by the find methods.
*
* @param array $row attribute values (column name=>column value) * @param array $row attribute values (column name=>column value)
*
* @return ActiveRecord the newly created active record. The class of the object is the same as the model class. * @return ActiveRecord the newly created active record. The class of the object is the same as the model class.
* Null is returned if the input data is false. * Null is returned if the input data is false.
*/ */
public static function populateData($row) public static function createRecord($row)
{ {
$record = static::instantiate($row); $record = static::instantiate($row);
$columns = static::getMetaData()->table->columns; $columns = static::getMetaData()->table->columns;
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name])) {
$record->_attributes[$name] = $value; $record->_attributes[$name] = $value;
} elseif ($record->canSetProperty($name)) {
$record->$name = $value;
} }
} }
$record->_oldAttributes = $record->_attributes; $record->_oldAttributes = $record->_attributes;
...@@ -969,7 +1049,7 @@ abstract class ActiveRecord extends Model ...@@ -969,7 +1049,7 @@ abstract class ActiveRecord extends Model
/** /**
* Creates an active record instance. * Creates an active record instance.
* This method is called by {@link populateData}. * This method is called by [[createRecord()]].
* You may override this method if the instance being created * You may override this method if the instance being created
* depends the attributes that are to be populated to the record. * depends the attributes that are to be populated to the record.
* For example, by creating a record based on the value of a column, * For example, by creating a record based on the value of a column,
......
...@@ -27,8 +27,10 @@ class CActiveRecordBehavior extends CModelBehavior ...@@ -27,8 +27,10 @@ class CActiveRecordBehavior extends CModelBehavior
public function events() public function events()
{ {
return array_merge(parent::events(), array( return array_merge(parent::events(), array(
'onBeforeSave' => 'beforeSave', 'onBeforeInsert' => 'beforeInsert',
'onAfterSave' => 'afterSave', 'onAfterInsert' => 'afterInsert',
'onBeforeUpdate' => 'beforeUpdate',
'onAfterUpdate' => 'afterUpdate',
'onBeforeDelete' => 'beforeDelete', 'onBeforeDelete' => 'beforeDelete',
'onAfterDelete' => 'afterDelete', 'onAfterDelete' => 'afterDelete',
'onBeforeFind' => 'beforeFind', 'onBeforeFind' => 'beforeFind',
...@@ -42,7 +44,7 @@ class CActiveRecordBehavior extends CModelBehavior ...@@ -42,7 +44,7 @@ class CActiveRecordBehavior extends CModelBehavior
* You may set {@link CModelEvent::isValid} to be false to quit the saving process. * You may set {@link CModelEvent::isValid} to be false to quit the saving process.
* @param CModelEvent $event event parameter * @param CModelEvent $event event parameter
*/ */
public function beforeSave($event) public function beforeInsert($event)
{ {
} }
...@@ -51,7 +53,26 @@ class CActiveRecordBehavior extends CModelBehavior ...@@ -51,7 +53,26 @@ class CActiveRecordBehavior extends CModelBehavior
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CModelEvent $event event parameter * @param CModelEvent $event event parameter
*/ */
public function afterSave($event) public function afterInsert($event)
{
}
/**
* Responds to {@link CActiveRecord::onBeforeSave} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* You may set {@link CModelEvent::isValid} to be false to quit the saving process.
* @param CModelEvent $event event parameter
*/
public function beforeUpdate($event)
{
}
/**
* Responds to {@link CActiveRecord::onAfterSave} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CModelEvent $event event parameter
*/
public function afterUpdate($event)
{ {
} }
......
<?php <?php
/**
* ActiveRelation class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\ar; namespace yii\db\ar;
class ActiveRelation extends \yii\base\Object use yii\db\dao\BaseQuery;
/**
* ActiveRelation represents the specification of a relation declared in [[ActiveRecord::relations()]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveRelation extends BaseQuery
{ {
/**
* @var string the name of this relation
*/
public $name; public $name;
public $modelClass;
public $hasMany;
public $joinType;
public $tableAlias;
public $on;
public $via;
public $with;
public $scopes;
/** /**
* @var string|array the columns being selected. This refers to the SELECT clause in a SQL * @var string the name of the model class that this relation represents
* 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; public $modelClass;
/** /**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement. * @var boolean whether this relation is a one-many relation
* For example, `age > 31 AND team = 1`.
* @see where()
*/ */
public $where; public $hasMany;
/** /**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. * @var string the join type (e.g. INNER JOIN, LEFT JOIN). Defaults to 'LEFT JOIN'.
*/ */
public $limit; public $joinType = 'LEFT JOIN';
/** /**
* @var integer zero-based offset from where the records are to be returned. If not set or * @var string the table alias used for the corresponding table during query
* less than 0, it means starting from the beginning.
*/ */
public $offset; public $tableAlias;
/** /**
* @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement. * @var string the name of the column that the result should be indexed by.
* It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`). * This is only useful when [[hasMany]] is true.
*/ */
public $orderBy; public $indexBy;
/** /**
* @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement. * @var string the ON clause of the join query
* It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
*/ */
public $groupBy; public $on;
/** /**
* @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement. * @var string
* It can 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; public $via;
/** /**
* @var string|array the condition to be applied in the GROUP BY clause. * @var array the relations that should be queried together (eager loading)
* It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
*/ */
public $having; public $with;
/** /**
* @var array list of query parameter values indexed by parameter placeholders. * @var array the scopes that should be applied during query
* For example, `array(':name'=>'Dan', ':age'=>31)`.
*/ */
public $params; public $scopes;
} }
<?php
/**
* BaseQuery class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* 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\Object
{
/**
* @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 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 array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
*/
public $params;
/**
* @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;
/**
* 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 addGroupBy()
*/
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 groupBy()
*/
public function addGroupBy($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 addOrderBy()
*/
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 orderBy()
*/
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.
*
* The merging is done according to the following rules:
*
* - [[select]]: the union of both queries' [[select]] property values.
* - [[selectOption]], [[distinct]], [[from]], [[limit]], [[offset]]: the new query
* takes precedence over this query.
* - [[where]], [[having]]: the new query's corresponding property value
* will be 'AND' together with the existing one.
* - [[params]], [[orderBy]], [[groupBy]], [[join]], [[union]]: the new query's
* corresponding property value will be appended to the existing one.
*
* In general, the merging makes the resulting query more restrictive and specific.
* @param BaseQuery $query the new query to be merged with this query.
* @return BaseQuery the query object itself
*/
public function mergeWith($query)
{
if ($this->select !== $query->select) {
if (empty($this->select)) {
$this->select = $query->select;
} elseif (!empty($query->select)) {
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select;
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if ($query->selectOption !== null) {
$this->selectOption = $query->selectOption;
}
if ($query->distinct !== null) {
$this->distinct = $query->distinct;
}
if ($query->from !== null) {
$this->from = $query->from;
}
if ($query->limit !== null) {
$this->limit = $query->limit;
}
if ($query->offset !== null) {
$this->offset = $query->offset;
}
if ($query->where !== null) {
$this->andWhere($query->where);
}
if ($query->having !== null) {
$this->andHaving($query->having);
}
if ($query->params !== null) {
$this->addParams($query->params);
}
if ($query->orderBy !== null) {
$this->addOrderBy($query->orderBy);
}
if ($query->groupBy !== null) {
$this->addGroupBy($query->groupBy);
}
if ($query->join !== null) {
if (empty($this->join)) {
$this->join = $query->join;
} else {
if (!is_array($this->join)) {
$this->join = array($this->join);
}
if (is_array($query->join)) {
$this->join = array_merge($this->join, $query->join);
} else {
$this->join[] = $query->join;
}
}
}
if ($query->union !== null) {
if (empty($this->union)) {
$this->union = $query->union;
} else {
if (!is_array($this->union)) {
$this->union = array($this->union);
}
if (is_array($query->union)) {
$this->union = array_merge($this->union, $query->union);
} else {
$this->union[] = $query->union;
}
}
}
return $this;
}
}
...@@ -64,27 +64,15 @@ class Command extends \yii\base\Component ...@@ -64,27 +64,15 @@ class Command extends \yii\base\Component
/** /**
* Constructor. * Constructor.
* Instead of using the `new` operator, you may use [[Connection::createCommand()]]
* to create a new Command object.
* @param Connection $connection the database connection * @param Connection $connection the database connection
* @param string|array|Query $query the DB query to be executed. This can be: * @param string $sql the SQL statement to be executed
* * @param array $params the parameters to be bound to the SQL statement
* - a string representing the SQL statement to be executed
* - a [[Query]] object representing the SQL query
* - an array that will be used to create and initialize the [[Query]] object
*/ */
public function __construct($connection, $query = null) public function __construct($connection, $sql = null, $params = array())
{ {
$this->connection = $connection; $this->connection = $connection;
if (is_array($query)) { $this->_sql = $sql;
$query = Query::newInstance($query); $this->bindValues($params);
}
if ($query instanceof Query) {
$this->_sql = $query->getSql($connection);
$this->bindValues($query->params);
} else {
$this->_sql = $query;
}
} }
/** /**
...@@ -233,8 +221,6 @@ class Command extends \yii\base\Component ...@@ -233,8 +221,6 @@ class Command extends \yii\base\Component
$paramLog = "\nParameters: " . var_export($this->_params, true); $paramLog = "\nParameters: " . var_export($this->_params, true);
} }
echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
\Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__); \Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
try { try {
...@@ -368,8 +354,6 @@ echo "Executing SQL: {$sql}{$paramLog}" . "\n\n"; ...@@ -368,8 +354,6 @@ echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
$paramLog = "\nParameters: " . var_export($this->_params, true); $paramLog = "\nParameters: " . var_export($this->_params, true);
} }
echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__); \Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') { if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') {
......
...@@ -399,17 +399,14 @@ class Connection extends \yii\base\ApplicationComponent ...@@ -399,17 +399,14 @@ class Connection extends \yii\base\ApplicationComponent
/** /**
* Creates a command for execution. * Creates a command for execution.
* @param string|array|Query $query the DB query to be executed. This can be: * @param string $sql the SQL statement to be executed
* * @param array $params the parameters to be bound to the SQL statement
* - a string representing the SQL statement to be executed
* - a [[Query]] object representing the SQL query
* - an array that will be used to initialize [[Query]]
* @return Command the DB command * @return Command the DB command
*/ */
public function createCommand($query = null) public function createCommand($sql = null, $params = array())
{ {
$this->open(); $this->open();
return new Command($this, $query); return new Command($this, $sql, $params);
} }
/** /**
......
...@@ -39,7 +39,7 @@ namespace yii\db\dao; ...@@ -39,7 +39,7 @@ namespace yii\db\dao;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Query extends \yii\base\Object class Query extends BaseQuery
{ {
/** /**
* @var array the operation that this query represents. This refers to the method call as well as * @var array the operation that this query represents. This refers to the method call as well as
...@@ -48,80 +48,11 @@ class Query extends \yii\base\Object ...@@ -48,80 +48,11 @@ class Query extends \yii\base\Object
* If this property is not set, it means this query represents a SELECT statement. * If this property is not set, it means this query represents a SELECT statement.
*/ */
public $operation; public $operation;
/**
* @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 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 array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
*/
public $params;
/**
* @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;
/** /**
* Generates and returns the SQL statement according to this query. * 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. * @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. * If this parameter is not given, the `db` application component will be used.
* @return string the generated SQL statement * @return string the generated SQL statement
...@@ -131,7 +62,15 @@ class Query extends \yii\base\Object ...@@ -131,7 +62,15 @@ class Query extends \yii\base\Object
if ($connection === null) { if ($connection === null) {
$connection = \Yii::$application->db; $connection = \Yii::$application->db;
} }
return $connection->getQueryBuilder()->build($this); $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 {
return $qb->build($this);
}
} }
/** /**
...@@ -145,7 +84,8 @@ class Query extends \yii\base\Object ...@@ -145,7 +84,8 @@ class Query extends \yii\base\Object
if ($connection === null) { if ($connection === null) {
$connection = \Yii::$application->db; $connection = \Yii::$application->db;
} }
return $connection->createCommand($this); $sql = $this->getSql($connection);
return $connection->createCommand($sql, $this->params);
} }
/** /**
...@@ -157,7 +97,7 @@ class Query extends \yii\base\Object ...@@ -157,7 +97,7 @@ class Query extends \yii\base\Object
*/ */
public function insert($table, $columns) public function insert($table, $columns)
{ {
$this->operation = array(__FUNCTION__, $table, $columns, array()); $this->operation = array(__FUNCTION__, $table, $columns);
return $this; return $this;
} }
...@@ -173,8 +113,7 @@ class Query extends \yii\base\Object ...@@ -173,8 +113,7 @@ class Query extends \yii\base\Object
*/ */
public function update($table, $columns, $condition = '', $params = array()) public function update($table, $columns, $condition = '', $params = array())
{ {
$this->addParams($params); $this->operation = array(__FUNCTION__, $table, $columns, $condition, $params);
$this->operation = array(__FUNCTION__, $table, $columns, $condition, array());
return $this; return $this;
} }
...@@ -188,8 +127,8 @@ class Query extends \yii\base\Object ...@@ -188,8 +127,8 @@ class Query extends \yii\base\Object
*/ */
public function delete($table, $condition = '', $params = array()) public function delete($table, $condition = '', $params = array())
{ {
$this->operation = array(__FUNCTION__, $table, $condition); $this->operation = array(__FUNCTION__, $table, $condition, $params);
return $this->addParams($params); return $this;
} }
/** /**
...@@ -361,559 +300,4 @@ class Query extends \yii\base\Object ...@@ -361,559 +300,4 @@ class Query extends \yii\base\Object
$this->operation = array(__FUNCTION__, $name, $table); $this->operation = array(__FUNCTION__, $name, $table);
return $this; return $this;
} }
/**
* 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 = '')
{
$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 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 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);
}
$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 Query 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 Query 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 Query 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 Query 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 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 addGroupBy()
*/
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 Query the query object itself
* @see groupBy()
*/
public function addGroupBy($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 Query 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 Query 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 Query 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 Query the query object itself
* @see addOrderBy()
*/
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 Query the query object itself
* @see orderBy()
*/
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 Query 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 Query the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* 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 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 Query 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 Query 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.
*
* The merging is done according to the following rules:
*
* - [[select]]: the union of both queries' [[select]] property values.
* - [[selectOption]], [[distinct]], [[from]], [[limit]], [[offset]]: the new query
* takes precedence over this query.
* - [[where]], [[having]]: the new query's corresponding property value
* will be 'AND' together with the existing one.
* - [[params]], [[orderBy]], [[groupBy]], [[join]], [[union]]: the new query's
* corresponding property value will be appended to the existing one.
*
* In general, the merging makes the resulting query more restrictive and specific.
* @param Query $query the new query to be merged with this query.
* @return Query the query object itself
*/
public function mergeWith($query)
{
if ($this->select !== $query->select) {
if (empty($this->select)) {
$this->select = $query->select;
} elseif (!empty($query->select)) {
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select;
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if ($query->selectOption !== null) {
$this->selectOption = $query->selectOption;
}
if ($query->distinct !== null) {
$this->distinct = $query->distinct;
}
if ($query->from !== null) {
$this->from = $query->from;
}
if ($query->limit !== null) {
$this->limit = $query->limit;
}
if ($query->offset !== null) {
$this->offset = $query->offset;
}
if ($query->where !== null) {
$this->andWhere($query->where);
}
if ($query->having !== null) {
$this->andHaving($query->having);
}
if ($query->params !== null) {
$this->addParams($query->params);
}
if ($query->orderBy !== null) {
$this->addOrderBy($query->orderBy);
}
if ($query->groupBy !== null) {
$this->addGroupBy($query->groupBy);
}
if ($query->join !== null) {
if (empty($this->join)) {
$this->join = $query->join;
} else {
if (!is_array($this->join)) {
$this->join = array($this->join);
}
if (is_array($query->join)) {
$this->join = array_merge($this->join, $query->join);
} else {
$this->join[] = $query->join;
}
}
}
if ($query->union !== null) {
if (empty($this->union)) {
$this->union = $query->union;
} else {
if (!is_array($this->union)) {
$this->union = array($this->union);
}
if (is_array($query->union)) {
$this->union = array_merge($this->union, $query->union);
} else {
$this->union[] = $query->union;
}
}
}
return $this;
}
/**
* Resets the query object to its original state.
* @return Query the query object itself
*/
public function reset()
{
foreach (get_object_vars($this) as $name => $value) {
$this->$name = null;
}
return $this;
}
} }
...@@ -13,9 +13,9 @@ namespace yii\db\dao; ...@@ -13,9 +13,9 @@ namespace yii\db\dao;
use yii\db\Exception; use yii\db\Exception;
/** /**
* QueryBuilder builds a SQL statement based on the specification given as a [[Query]] object. * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[BaseQuery]] object.
* *
* QueryBuilder is often used behind the scenes by [[Query]] to build a DBMS-dependent SQL statement * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE,
* from a [[Query]] object. * from a [[Query]] object.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
...@@ -57,22 +57,13 @@ class QueryBuilder extends \yii\base\Object ...@@ -57,22 +57,13 @@ class QueryBuilder extends \yii\base\Object
} }
/** /**
* Generates a SQL statement from a [[Query]] object. * Generates a SELECT SQL statement from a [[BaseQuery]] object.
* Note that when generating SQL statements for INSERT and UPDATE queries, * @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated
* the query object's [[Query::params]] property may be appended with new parameters.
* @param Query $query the [[Query]] object from which the SQL statement will be generated
* @return string the generated SQL statement * @return string the generated SQL statement
*/ */
public function build($query) public function build($query)
{ {
$this->query = $query; $this->query = $query;
if ($query->operation !== null) {
// non-SELECT query
$params = $query->operation;
$method = array_shift($params);
return call_user_func_array(array($this, $method), $params);
} else {
// SELECT query
$clauses = array( $clauses = array(
$this->buildSelect(), $this->buildSelect(),
$this->buildFrom(), $this->buildFrom(),
...@@ -86,7 +77,6 @@ class QueryBuilder extends \yii\base\Object ...@@ -86,7 +77,6 @@ class QueryBuilder extends \yii\base\Object
); );
return implode($this->separator, array_filter($clauses)); return implode($this->separator, array_filter($clauses));
} }
}
/** /**
* Creates and executes an INSERT SQL statement. * Creates and executes an INSERT SQL statement.
...@@ -123,7 +113,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -123,7 +113,7 @@ class QueryBuilder extends \yii\base\Object
$count++; $count++;
} }
} }
if ($this->query instanceof Query) { if ($this->query instanceof BaseQuery) {
$this->query->addParams($params); $this->query->addParams($params);
} }
...@@ -167,7 +157,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -167,7 +157,7 @@ class QueryBuilder extends \yii\base\Object
$count++; $count++;
} }
} }
if ($this->query instanceof Query) { if ($this->query instanceof BaseQuery) {
$this->query->addParams($params); $this->query->addParams($params);
} }
$sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines); $sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines);
...@@ -189,14 +179,18 @@ class QueryBuilder extends \yii\base\Object ...@@ -189,14 +179,18 @@ class QueryBuilder extends \yii\base\Object
* @param string $table the table where the data will be deleted from. * @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 * @param mixed $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition. * refer to [[Query::where()]] on how to specify condition.
* @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution. * @return integer number of rows affected by the execution.
*/ */
public function delete($table, $condition = '') public function delete($table, $condition = '', $params = array())
{ {
$sql = 'DELETE FROM ' . $this->quoteTableName($table); $sql = 'DELETE FROM ' . $this->quoteTableName($table);
if (($where = $this->buildCondition($condition)) != '') { if (($where = $this->buildCondition($condition)) != '') {
$sql .= ' WHERE ' . $where; $sql .= ' WHERE ' . $where;
} }
if ($params !== array() && $this->query instanceof BaseQuery) {
$this->query->addParams($params);
}
return $sql; return $sql;
} }
...@@ -631,7 +625,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -631,7 +625,7 @@ class QueryBuilder extends \yii\base\Object
protected function buildSelect() protected function buildSelect()
{ {
$select = $this->query->distinct ? 'SELECT DISTINCT' : 'SELECT'; $select = $this->query->distinct ? 'SELECT DISTINCT' : 'SELECT';
if ($this->query->selectOption != '') { if ($this->query->selectOption !== null) {
$select .= ' ' . $this->query->selectOption; $select .= ' ' . $this->query->selectOption;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment