Browse Source

...

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
965fe5c4d2
  1. 453
      framework/db/ar/ActiveQuery.php
  2. 602
      framework/db/ar/ActiveRecord.php
  3. 29
      framework/db/ar/ActiveRecordBehavior.php
  4. 81
      framework/db/ar/ActiveRelation.php
  5. 635
      framework/db/dao/BaseQuery.php
  6. 26
      framework/db/dao/Command.php
  7. 11
      framework/db/dao/Connection.php
  8. 652
      framework/db/dao/Query.php
  9. 54
      framework/db/dao/QueryBuilder.php

453
framework/db/ar/ActiveQuery.php

@ -0,0 +1,453 @@
<?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);
}
}
}

602
framework/db/ar/ActiveRecord.php

@ -38,6 +38,10 @@ abstract class ActiveRecord extends Model
* @var array old attribute values indexed by attribute names.
*/
private $_oldAttributes;
/**
* @var array related records indexed by relation names.
*/
private $_related;
/**
* Returns the metadata for this AR class.
@ -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,
* additional query conditions can be specified by calling these methods.
* Because [[ActiveQuery]] implements a set of query building methods,
* additional query conditions can be specified by calling the methods of [[ActiveQuery]].
*
* Below are some usage examples:
*
* ~~~
* // find all customers
* $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();
* // 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:
*
* - a scalar value (integer, string): query by a single primary key value.
* - an array of name-value pairs: query by a set of column values.
* - a [[Query]] object: query by a full query object.
* - 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.
*
* @return ActiveFinder the [[ActiveFinder]] instance for query purpose.
* @throws Exception if the query parameter is invalid.
* @return ActiveQuery the [[ActiveQuery]] instance for query purpose.
*/
public static function find($q = null)
{
$finder = static::createActiveFinder();
if ($q instanceof Query) {
$finder->query = $q;
} elseif (is_array($q)) {
// query by a set of column values
$finder->where($q);
$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;
if (count($primaryKey) === 1) {
$finder->where(array($primaryKey[0] => $q));
} else {
throw new Exception('Multiple values are required to query by composite primary keys.');
}
$query->where(array($primaryKey[0] => $q));
}
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
* 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 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())
{
$finder = static::createActiveFinder();
$finder->sql = $sql;
return $finder->params($params);
$query = static::createActiveQuery();
$query->sql = $sql;
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())
{
$class = get_called_class();
$query = new Query;
$query->update($class::tableName(), $attributes, $condition, $params);
return $query->createCommand($class::getDbConnection())->execute();
$query->update(static::tableName(), $attributes, $condition, $params);
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 = $class::getDbConnection();
$db = static::getDbConnection();
foreach ($counters as $name => $value) {
$value = (int)$value;
$quotedName = $db->quoteColumnName($name, true);
$counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value");
}
$query = new Query;
$query->update($class::tableName(), $counters, $condition, $params);
return $query->createCommand($class::getDbConnection())->execute();
$query->update(static::tableName(), $counters, $condition, $params);
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())
{
$class = get_called_class();
$query = new Query;
$query->delete($class::tableName(), $condition, $params);
return $query->createCommand($class::getDbConnection())->execute();
$query->delete(static::tableName(), $condition, $params);
return $query->createCommand(static::getDbConnection())->execute();
}
/**
* Creates a [[ActiveFinder]] instance.
* This method is mainly called by [[find()]] and [[findBySql()]].
* @return ActiveFinder the newly created [[ActiveFinder]] instance.
* Creates a [[ActiveQuery]] instance.
* This method is called by [[find()]] and [[findBySql()]] to start a SELECT query.
* @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
/**
* 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
* (for some legacy database). If the table is already 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 already has a primary key,
* you do not need to override this method. The default implementation simply returns null,
* meaning using the primary key defined in the database table.
* @return string|array the primary key of the associated database table.
@ -193,7 +260,7 @@ abstract class ActiveRecord extends Model
*
* 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(
@ -350,14 +417,17 @@ abstract class ActiveRecord extends Model
*/
public function __get($name)
{
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
if (isset($this->_attributes[$name])) {
return $this->_attributes[$name];
} else {
$md = $this->getMetaData();
if (isset($md->table->columns[$name])) {
return null;
} elseif (isset($md->relations[$name])) {
return $this->_attributes[$name] = $this->loadRelatedRecord($md->relations[$name]);
}
$md = $this->getMetaData();
if (isset($md->table->columns[$name])) {
return null;
} elseif (isset($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);
@ -372,8 +442,10 @@ abstract class ActiveRecord extends Model
public function __set($name, $value)
{
$md = $this->getMetaData();
if (isset($md->table->columns[$name]) || isset($md->relations[$name])) {
if (isset($md->table->columns[$name])) {
$this->_attributes[$name] = $value;
} elseif (isset($md->relations[$name])) {
$this->_related[$name] = $value;
} else {
parent::__set($name, $value);
}
@ -388,9 +460,11 @@ abstract class ActiveRecord extends Model
*/
public function __isset($name)
{
if (isset($this->_attributes[$name])) {
if (isset($this->_attributes[$name]) || isset($this->_related[$name])) {
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;
} else {
return parent::__isset($name);
@ -406,8 +480,10 @@ abstract class ActiveRecord extends Model
public function __unset($name)
{
$md = $this->getMetaData();
if (isset($md->table->columns[$name]) || isset($md->relations[$name])) {
if (isset($md->table->columns[$name])) {
unset($this->_attributes[$name]);
} elseif (isset($md->relations[$name])) {
unset($this->_related[$name]);
} else {
parent::__unset($name);
}
@ -432,20 +508,20 @@ abstract class ActiveRecord extends Model
/**
* 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
*/
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)
{
if ($relation->hasMany) {
$this->_attributes[$relation->name][] = $record;
$this->_related[$relation->name][] = $record;
} else {
$this->_attributes[$relation->name] = $record;
$this->_related[$relation->name] = $record;
}
}
@ -472,7 +548,7 @@ abstract class ActiveRecord extends Model
}
$relation = $md->relations[$relation];
}
$finder = $this->createActiveFinder();
$finder = $this->createActiveQuery();
}
/**
@ -541,7 +617,7 @@ abstract class ActiveRecord extends Model
}
$names = array_flip($names);
$attributes = array();
if (empty($this->_oldAttributes)) {
if ($this->_oldAttributes === null) {
foreach ($this->_attributes as $name => $value) {
if (isset($names[$name])) {
$attributes[$name] = $value;
@ -584,6 +660,124 @@ abstract class ActiveRecord extends Model
{
if (!$runValidation || $this->validate($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 {
return false;
}
@ -598,7 +792,7 @@ abstract class ActiveRecord extends Model
*/
public function getIsNewRecord()
{
return empty($this->_oldAttributes);
return $this->_oldAttributes === null;
}
/**
@ -608,41 +802,51 @@ abstract class ActiveRecord extends Model
*/
public function setIsNewRecord($value)
{
if ($value) {
$this->_oldAttributes = null;
} else {
$this->_oldAttributes = array();
foreach ($this->attributeNames() as $name) {
if (isset($this->_attributes[$name])) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
}
}
$this->_oldAttributes = $value ? null : $this->_attributes;
}
/**
* 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.
* @param ModelEvent $event the event parameter
* 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 onBeforeSave($event)
public function onBeforeInsert($event)
{
$this->raiseEvent('onBeforeSave', $event);
$this->raiseEvent('onBeforeInsert', $event);
}
/**
* 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 onAfterInsert($event)
{
$this->raiseEvent('onAfterSave', $event);
$this->raiseEvent('onAfterInsert', $event);
}
/**
* 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 onBeforeUpdate($event)
{
$this->raiseEvent('onBeforeUpdate', $event);
}
/**
* This event is raised after the record is saved.
* @param \yii\base\Event $event the event parameter
*/
public function onAfterUpdate($event)
{
$this->raiseEvent('onAfterUpdate', $event);
}
/**
* 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.
* @param ModelEvent $event the event parameter
* By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped.
* @param \yii\base\ModelEvent $event the event parameter
*/
public function onBeforeDelete($event)
{
@ -651,7 +855,7 @@ abstract class ActiveRecord extends Model
/**
* 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)
{
@ -667,10 +871,37 @@ abstract class ActiveRecord extends Model
* 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 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);
$this->onBeforeSave($event);
$this->onBeforeUpdate($event);
return $event->isValid;
}
@ -680,9 +911,9 @@ abstract class ActiveRecord extends Model
* 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 afterSave()
public function afterUpdate()
{
$this->onAfterSave(new Event($this));
$this->onAfterUpdate(new Event($this));
}
/**
@ -711,182 +942,30 @@ 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.
* @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.
*/
public function refresh($attributes = null)
{
if (!$this->getIsNewRecord() && ($record = $this->find($this->getPrimaryKey(true))) !== null) {
if ($attributes === null) {
$attributes = $this->attributeNames();
}
$this->_attributes = array();
foreach ($attributes as $name) {
if ($this->getIsNewRecord()) {
return false;
}
$record = $this->find()->where($this->getPrimaryKey(true))->one();
if ($record === null) {
return false;
}
if ($attributes === null) {
foreach ($this->attributeNames() as $name) {
$this->_attributes[$name] = $record->_attributes[$name];
}
$this->_oldAttributes = $this->_attributes;
return true;
} else {
return false;
foreach ($attributes as $name) {
$this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name];
}
}
return true;
}
/**
@ -911,11 +990,11 @@ abstract class ActiveRecord extends Model
{
$table = static::getMetaData()->table;
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 {
$values = array();
foreach ($table->primaryKey as $name) {
$values[$name] = $this->$name;
$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
}
return $values;
}
@ -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.
* @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.
* @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.
*/
public function getOldPrimaryKey($asArray = false)
@ -948,19 +1028,19 @@ abstract class ActiveRecord extends Model
/**
* Creates an active record with the given attributes.
* This method is internally used by the find methods.
*
* @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.
* Null is returned if the input data is false.
*/
public static function populateData($row)
public static function createRecord($row)
{
$record = static::instantiate($row);
$columns = static::getMetaData()->table->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$record->_attributes[$name] = $value;
} elseif ($record->canSetProperty($name)) {
$record->$name = $value;
}
}
$record->_oldAttributes = $record->_attributes;
@ -969,7 +1049,7 @@ abstract class ActiveRecord extends Model
/**
* 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
* depends the attributes that are to be populated to the record.
* For example, by creating a record based on the value of a column,

29
framework/db/ar/ActiveRecordBehavior.php

@ -27,8 +27,10 @@ class CActiveRecordBehavior extends CModelBehavior
public function events()
{
return array_merge(parent::events(), array(
'onBeforeSave' => 'beforeSave',
'onAfterSave' => 'afterSave',
'onBeforeInsert' => 'beforeInsert',
'onAfterInsert' => 'afterInsert',
'onBeforeUpdate' => 'beforeUpdate',
'onAfterUpdate' => 'afterUpdate',
'onBeforeDelete' => 'beforeDelete',
'onAfterDelete' => 'afterDelete',
'onBeforeFind' => 'beforeFind',
@ -42,7 +44,7 @@ class CActiveRecordBehavior extends CModelBehavior
* You may set {@link CModelEvent::isValid} to be false to quit the saving process.
* @param CModelEvent $event event parameter
*/
public function beforeSave($event)
public function beforeInsert($event)
{
}
@ -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}.
* @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)
{
}

81
framework/db/ar/ActiveRelation.php

@ -1,67 +1,64 @@
<?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;
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 $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
* 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()
* @var string the name of the model class that this relation represents
*/
public $select;
public $modelClass;
/**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement.
* For example, `age > 31 AND team = 1`.
* @see where()
* @var boolean whether this relation is a one-many relation
*/
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
* less than 0, it means starting from the beginning.
* @var string the table alias used for the corresponding table during query
*/
public $offset;
public $tableAlias;
/**
* @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')`).
* @var string the name of the column that the result should be indexed by.
* 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.
* It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
* @var string the ON clause of the join query
*/
public $groupBy;
public $on;
/**
* @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()
* @var string
*/
public $join;
public $via;
/**
* @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.
* @var array the relations that should be queried together (eager loading)
*/
public $having;
public $with;
/**
* @var array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
* @var array the scopes that should be applied during query
*/
public $params;
public $scopes;
}

635
framework/db/dao/BaseQuery.php

@ -0,0 +1,635 @@
<?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;
}
}

26
framework/db/dao/Command.php

@ -64,27 +64,15 @@ class Command extends \yii\base\Component
/**
* 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 string|array|Query $query the DB query to be executed. This can be:
*
* - 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
* @param string $sql the SQL statement to be executed
* @param array $params the parameters to be bound to the SQL statement
*/
public function __construct($connection, $query = null)
public function __construct($connection, $sql = null, $params = array())
{
$this->connection = $connection;
if (is_array($query)) {
$query = Query::newInstance($query);
}
if ($query instanceof Query) {
$this->_sql = $query->getSql($connection);
$this->bindValues($query->params);
} else {
$this->_sql = $query;
}
$this->_sql = $sql;
$this->bindValues($params);
}
/**
@ -233,8 +221,6 @@ class Command extends \yii\base\Component
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
\Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
try {
@ -368,8 +354,6 @@ echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
echo "Executing SQL: {$sql}{$paramLog}" . "\n\n";
\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') {

11
framework/db/dao/Connection.php

@ -399,17 +399,14 @@ class Connection extends \yii\base\ApplicationComponent
/**
* Creates a command for execution.
* @param string|array|Query $query the DB query to be executed. This can be:
*
* - 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]]
* @param string $sql the SQL statement to be executed
* @param array $params the parameters to be bound to the SQL statement
* @return Command the DB command
*/
public function createCommand($query = null)
public function createCommand($sql = null, $params = array())
{
$this->open();
return new Command($this, $query);
return new Command($this, $sql, $params);
}
/**

652
framework/db/dao/Query.php

@ -39,7 +39,7 @@ namespace yii\db\dao;
* @author Qiang Xue <qiang.xue@gmail.com>
* @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
@ -48,80 +48,11 @@ class Query extends \yii\base\Object
* If this property is not set, it means this query represents a SELECT statement.
*/
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.
* 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.
* If this parameter is not given, the `db` application component will be used.
* @return string the generated SQL statement
@ -131,7 +62,15 @@ class Query extends \yii\base\Object
if ($connection === null) {
$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
if ($connection === null) {
$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
*/
public function insert($table, $columns)
{
$this->operation = array(__FUNCTION__, $table, $columns, array());
$this->operation = array(__FUNCTION__, $table, $columns);
return $this;
}
@ -173,8 +113,7 @@ class Query extends \yii\base\Object
*/
public function update($table, $columns, $condition = '', $params = array())
{
$this->addParams($params);
$this->operation = array(__FUNCTION__, $table, $columns, $condition, array());
$this->operation = array(__FUNCTION__, $table, $columns, $condition, $params);
return $this;
}
@ -188,8 +127,8 @@ class Query extends \yii\base\Object
*/
public function delete($table, $condition = '', $params = array())
{
$this->operation = array(__FUNCTION__, $table, $condition);
return $this->addParams($params);
$this->operation = array(__FUNCTION__, $table, $condition, $params);
return $this;
}
/**
@ -361,559 +300,4 @@ class Query extends \yii\base\Object
$this->operation = array(__FUNCTION__, $name, $table);
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;
}
}

54
framework/db/dao/QueryBuilder.php

@ -13,9 +13,9 @@ namespace yii\db\dao;
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.
*
* @author Qiang Xue <qiang.xue@gmail.com>
@ -57,35 +57,25 @@ class QueryBuilder extends \yii\base\Object
}
/**
* Generates a SQL statement from a [[Query]] object.
* Note that when generating SQL statements for INSERT and UPDATE queries,
* 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
* Generates a SELECT SQL statement from a [[BaseQuery]] object.
* @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated
* @return string the generated SQL statement
*/
public function build($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(
$this->buildSelect(),
$this->buildFrom(),
$this->buildJoin(),
$this->buildWhere(),
$this->buildGroupBy(),
$this->buildHaving(),
$this->buildUnion(),
$this->buildOrderBy(),
$this->buildLimit(),
);
return implode($this->separator, array_filter($clauses));
}
$clauses = array(
$this->buildSelect(),
$this->buildFrom(),
$this->buildJoin(),
$this->buildWhere(),
$this->buildGroupBy(),
$this->buildHaving(),
$this->buildUnion(),
$this->buildOrderBy(),
$this->buildLimit(),
);
return implode($this->separator, array_filter($clauses));
}
/**
@ -123,7 +113,7 @@ class QueryBuilder extends \yii\base\Object
$count++;
}
}
if ($this->query instanceof Query) {
if ($this->query instanceof BaseQuery) {
$this->query->addParams($params);
}
@ -167,7 +157,7 @@ class QueryBuilder extends \yii\base\Object
$count++;
}
}
if ($this->query instanceof Query) {
if ($this->query instanceof BaseQuery) {
$this->query->addParams($params);
}
$sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines);
@ -189,14 +179,18 @@ class QueryBuilder extends \yii\base\Object
* @param string $table the table where the data will be deleted from.
* @param mixed $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition.
* @param array $params the parameters to be bound to the query.
* @return 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);
if (($where = $this->buildCondition($condition)) != '') {
$sql .= ' WHERE ' . $where;
}
if ($params !== array() && $this->query instanceof BaseQuery) {
$this->query->addParams($params);
}
return $sql;
}
@ -631,7 +625,7 @@ class QueryBuilder extends \yii\base\Object
protected function buildSelect()
{
$select = $this->query->distinct ? 'SELECT DISTINCT' : 'SELECT';
if ($this->query->selectOption != '') {
if ($this->query->selectOption !== null) {
$select .= ' ' . $this->query->selectOption;
}

Loading…
Cancel
Save