From eaf60e14f5ca431b1d0e602bdbcc564eb3f97c92 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 1 Aug 2011 15:59:10 -0400 Subject: [PATCH] w --- framework/db/ar/ActiveRecord.php | 164 +++-- framework/db/dao/Command.php | 1499 ++++++++++++++++++++++++++++++++++++++ framework/db/dao/Connection.php | 794 ++++++++++++++++++++ framework/db/dao/DataReader.php | 241 ++++++ framework/db/dao/Transaction.php | 108 +++ framework/db/pdo/Command.php | 1499 -------------------------------------- framework/db/pdo/Connection.php | 792 -------------------- framework/db/pdo/DataReader.php | 241 ------ framework/db/pdo/Transaction.php | 108 --- todo.txt | 1 + 10 files changed, 2736 insertions(+), 2711 deletions(-) create mode 100644 framework/db/dao/Command.php create mode 100644 framework/db/dao/Connection.php create mode 100644 framework/db/dao/DataReader.php create mode 100644 framework/db/dao/Transaction.php delete mode 100644 framework/db/pdo/Command.php delete mode 100644 framework/db/pdo/Connection.php delete mode 100644 framework/db/pdo/DataReader.php delete mode 100644 framework/db/pdo/Transaction.php diff --git a/framework/db/ar/ActiveRecord.php b/framework/db/ar/ActiveRecord.php index cbd1618..b0a8601 100644 --- a/framework/db/ar/ActiveRecord.php +++ b/framework/db/ar/ActiveRecord.php @@ -1,6 +1,6 @@ * @link http://www.yiiframework.com/ @@ -8,27 +8,61 @@ * @license http://www.yiiframework.com/license/ */ +namespace yii\db\ar; + +/* +1. query +$finder = Post::model()->find(array( + 'select' => 'id, title', + 'where' => 'id=123', + 'with' => array( + 'creator' => array( + + ), + ), +)); +$post = $finder->first(); +$posts = $finder->all(); +foreach($finder as $post) { + +} +foreach($finder->next(5) as $posts) { + +} + + +2. insert +$post = Post::create(); +$post->attributes = $_POST['Post']; +$post->save(); + +3. update + +4. delete + + +*/ + /** - * CActiveRecord is the base class for classes representing relational data. + * ActiveRecord is the base class for classes representing relational data. * * It implements the active record design pattern, a popular Object-Relational Mapping (ORM) technique. * Please check {@link http://www.yiiframework.com/doc/guide/database.ar the Guide} for more details * about this class. * * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 * * @property array $attributes */ -abstract class CActiveRecord extends CModel +abstract class ActiveRecord extends \yii\base\Model { const BELONGS_TO = 'CBelongsToRelation'; const HAS_ONE = 'CHasOneRelation'; const HAS_MANY = 'CHasManyRelation'; const MANY_MANY = 'CManyManyRelation'; - const STAT = 'CStatRelation'; /** * @var CDbConnection the default database connection for all active record classes. @@ -68,18 +102,6 @@ abstract class CActiveRecord extends CModel } /** - * Initializes this model. - * This method is invoked when an AR instance is newly created and has - * its {@link scenario} set. - * You may override this method to provide code that is needed to initialize the model (e.g. setting - * initial property values.) - * @since 1.0.8 - */ - public function init() - { - } - - /** * 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. @@ -88,7 +110,7 @@ abstract class CActiveRecord extends CModel * @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 CActiveRecord the active record instance itself. + * @return ActiveRecord the active record instance itself. * @since 1.1.7 */ public function cache($duration, $dependency = null, $queryCount = 1) @@ -239,7 +261,7 @@ abstract class CActiveRecord extends CModel throw new CDbException(Yii::t('yii', '{class} does not have relation "{name}".', array('{class}' => get_class($this), '{name}' => $name))); - Yii::trace('lazy loading ' . get_class($this) . '.' . $name, 'system.db.ar.CActiveRecord'); + Yii::trace('lazy loading ' . get_class($this) . '.' . $name, 'system.db.ar.ActiveRecord'); $relation = $md->relations[$name]; if ($this->getIsNewRecord() && !$refresh && ($relation instanceof CHasOneRelation || $relation instanceof CHasManyRelation)) return $relation instanceof CHasOneRelation ? null : array(); @@ -337,7 +359,7 @@ abstract class CActiveRecord extends CModel /** * Resets all scopes and criterias applied including default scope. * - * @return CActiveRecord + * @return ActiveRecord * @since 1.1.2 */ public function resetScope() @@ -360,7 +382,7 @@ abstract class CActiveRecord extends CModel * * * @param string $className active record class name. - * @return CActiveRecord active record model instance. + * @return ActiveRecord active record model instance. */ public static function model($className = __CLASS__) { @@ -369,7 +391,7 @@ abstract class CActiveRecord extends CModel else { $model = self::$_models[$className] = new $className(null); - $model->_md = new CActiveRecordMetaData($model); + $model->_md = new ActiveRecordMetaData($model); $model->attachBehaviors($model->behaviors()); return $model; } @@ -377,7 +399,7 @@ abstract class CActiveRecord extends CModel /** * Returns the meta-data for this AR - * @return CActiveRecordMetaData the meta for this AR class. + * @return ActiveRecordMetaData the meta for this AR class. */ public function getMetaData() { @@ -398,7 +420,7 @@ abstract class CActiveRecord extends CModel public function refreshMetaData() { $finder = self::model(get_class($this)); - $finder->_md = new CActiveRecordMetaData($finder); + $finder->_md = new ActiveRecordMetaData($finder); if ($this !== $finder) $this->_md = $finder->_md; } @@ -587,7 +609,7 @@ abstract class CActiveRecord extends CModel { $relations = $model->getMetaData()->relations; if (isset($relations[$seg])) - $model = CActiveRecord::model($relations[$seg]->className); + $model = ActiveRecord::model($relations[$seg]->className); else break; } @@ -708,7 +730,7 @@ abstract class CActiveRecord extends CModel { if (!isset($this->_related[$name])) $this->_related[$name] = array(); - if ($record instanceof CActiveRecord) + if ($record instanceof ActiveRecord) { if ($index === true) $this->_related[$name][] = $record; @@ -1013,7 +1035,7 @@ abstract class CActiveRecord extends CModel throw new CDbException(Yii::t('yii', 'The active record cannot be inserted to database because it is not new.')); if ($this->beforeSave()) { - Yii::trace(get_class($this) . '.insert()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.insert()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $table = $this->getMetaData()->tableSchema; $command = $builder->createInsertCommand($table, $this->getAttributes($attributes)); @@ -1061,7 +1083,7 @@ abstract class CActiveRecord extends CModel throw new CDbException(Yii::t('yii', 'The active record cannot be updated because it is new.')); if ($this->beforeSave()) { - Yii::trace(get_class($this) . '.update()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.update()', 'system.db.ar.ActiveRecord'); if ($this->_pk === null) $this->_pk = $this->getPrimaryKey(); $this->updateByPk($this->getOldPrimaryKey(), $this->getAttributes($attributes)); @@ -1095,7 +1117,7 @@ abstract class CActiveRecord extends CModel { if (!$this->getIsNewRecord()) { - Yii::trace(get_class($this) . '.saveAttributes()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.saveAttributes()', 'system.db.ar.ActiveRecord'); $values = array(); foreach ($attributes as $name => $value) { @@ -1135,7 +1157,7 @@ abstract class CActiveRecord extends CModel */ public function saveCounters($counters) { - Yii::trace(get_class($this) . '.saveCounters()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.saveCounters()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $table = $this->getTableSchema(); $criteria = $builder->createPkCriteria($table, $this->getOldPrimaryKey()); @@ -1159,7 +1181,7 @@ abstract class CActiveRecord extends CModel { if (!$this->getIsNewRecord()) { - Yii::trace(get_class($this) . '.delete()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.delete()', 'system.db.ar.ActiveRecord'); if ($this->beforeDelete()) { $result = $this->deleteByPk($this->getPrimaryKey()) > 0; @@ -1179,7 +1201,7 @@ abstract class CActiveRecord extends CModel */ public function refresh() { - Yii::trace(get_class($this) . '.refresh()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.refresh()', 'system.db.ar.ActiveRecord'); if (!$this->getIsNewRecord() && ($record = $this->findByPk($this->getPrimaryKey())) !== null) { $this->_attributes = array(); @@ -1200,7 +1222,7 @@ abstract class CActiveRecord extends CModel /** * Compares current active record with another one. * The comparison is made by comparing table name and the primary key values of the two active records. - * @param CActiveRecord $record record to compare to + * @param ActiveRecord $record record to compare to * @return boolean whether the two active records refer to the same row in the database table. */ public function equals($record) @@ -1390,11 +1412,11 @@ abstract class CActiveRecord extends CModel * @param array $params parameters to be bound to an SQL statement. * This is only used when the first parameter is a string (query condition). * In other cases, please use {@link CDbCriteria::params} to set parameters. - * @return CActiveRecord the record found. Null if no record is found. + * @return ActiveRecord the record found. Null if no record is found. */ public function find($condition = '', $params = array()) { - Yii::trace(get_class($this) . '.find()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.find()', 'system.db.ar.ActiveRecord'); $criteria = $this->getCommandBuilder()->createCriteria($condition, $params); return $this->query($criteria); } @@ -1408,7 +1430,7 @@ abstract class CActiveRecord extends CModel */ public function findAll($condition = '', $params = array()) { - Yii::trace(get_class($this) . '.findAll()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findAll()', 'system.db.ar.ActiveRecord'); $criteria = $this->getCommandBuilder()->createCriteria($condition, $params); return $this->query($criteria, true); } @@ -1419,11 +1441,11 @@ abstract class CActiveRecord extends CModel * @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. + * @return ActiveRecord the record found. Null if none is found. */ public function findByPk($pk, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.findByPk()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findByPk()', 'system.db.ar.ActiveRecord'); $prefix = $this->getTableAlias(true) . '.'; $criteria = $this->getCommandBuilder()->createPkCriteria($this->getTableSchema(), $pk, $condition, $params, $prefix); return $this->query($criteria); @@ -1439,7 +1461,7 @@ abstract class CActiveRecord extends CModel */ public function findAllByPk($pk, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.findAllByPk()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findAllByPk()', 'system.db.ar.ActiveRecord'); $prefix = $this->getTableAlias(true) . '.'; $criteria = $this->getCommandBuilder()->createPkCriteria($this->getTableSchema(), $pk, $condition, $params, $prefix); return $this->query($criteria, true); @@ -1452,11 +1474,11 @@ abstract class CActiveRecord extends CModel * Since version 1.0.8, an attribute value can be an array which will be used to generate an IN condition. * @param mixed $condition query condition or criteria. * @param array $params parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. + * @return ActiveRecord the record found. Null if none is found. */ public function findByAttributes($attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.findByAttributes()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findByAttributes()', 'system.db.ar.ActiveRecord'); $prefix = $this->getTableAlias(true) . '.'; $criteria = $this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix); return $this->query($criteria); @@ -1473,7 +1495,7 @@ abstract class CActiveRecord extends CModel */ public function findAllByAttributes($attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.findAllByAttributes()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findAllByAttributes()', 'system.db.ar.ActiveRecord'); $prefix = $this->getTableAlias(true) . '.'; $criteria = $this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix); return $this->query($criteria, true); @@ -1483,11 +1505,11 @@ abstract class CActiveRecord extends CModel * Finds a single active record with the specified SQL statement. * @param string $sql the SQL statement * @param array $params parameters to be bound to the SQL statement - * @return CActiveRecord the record found. Null if none is found. + * @return ActiveRecord the record found. Null if none is found. */ public function findBySql($sql, $params = array()) { - Yii::trace(get_class($this) . '.findBySql()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findBySql()', 'system.db.ar.ActiveRecord'); $this->beforeFind(); if (($criteria = $this->getDbCriteria(false)) !== null && !empty($criteria->with)) { @@ -1510,7 +1532,7 @@ abstract class CActiveRecord extends CModel */ public function findAllBySql($sql, $params = array()) { - Yii::trace(get_class($this) . '.findAllBySql()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.findAllBySql()', 'system.db.ar.ActiveRecord'); $this->beforeFind(); if (($criteria = $this->getDbCriteria(false)) !== null && !empty($criteria->with)) { @@ -1534,7 +1556,7 @@ abstract class CActiveRecord extends CModel */ public function count($condition = '', $params = array()) { - Yii::trace(get_class($this) . '.count()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.count()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createCriteria($condition, $params); $this->applyScopes($criteria); @@ -1560,7 +1582,7 @@ abstract class CActiveRecord extends CModel */ public function countByAttributes($attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.countByAttributes()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.countByAttributes()', 'system.db.ar.ActiveRecord'); $prefix = $this->getTableAlias(true) . '.'; $builder = $this->getCommandBuilder(); $criteria = $builder->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix); @@ -1585,7 +1607,7 @@ abstract class CActiveRecord extends CModel */ public function countBySql($sql, $params = array()) { - Yii::trace(get_class($this) . '.countBySql()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.countBySql()', 'system.db.ar.ActiveRecord'); return $this->getCommandBuilder()->createSqlCommand($sql, $params)->queryScalar(); } @@ -1598,7 +1620,7 @@ abstract class CActiveRecord extends CModel */ public function exists($condition = '', $params = array()) { - Yii::trace(get_class($this) . '.exists()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.exists()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createCriteria($condition, $params); $table = $this->getTableSchema(); @@ -1644,7 +1666,7 @@ abstract class CActiveRecord extends CModel * Previously, it was not possible to specify on-th-fly query options, * and child-relations were specified as hierarchical arrays. * - * @return CActiveRecord the AR object itself. + * @return ActiveRecord the AR object itself. */ public function with() { @@ -1663,7 +1685,7 @@ abstract class CActiveRecord extends CModel * Sets {@link CDbCriteria::together} property to be true. * This is only used in relational AR query. Please refer to {@link CDbCriteria::together} * for more details. - * @return CActiveRecord the AR object itself + * @return ActiveRecord the AR object itself * @since 1.1.4 */ public function together() @@ -1684,7 +1706,7 @@ abstract class CActiveRecord extends CModel */ public function updateByPk($pk, $attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.updateByPk()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.updateByPk()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $table = $this->getTableSchema(); $criteria = $builder->createPkCriteria($table, $pk, $condition, $params); @@ -1703,7 +1725,7 @@ abstract class CActiveRecord extends CModel */ public function updateAll($attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.updateAll()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.updateAll()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createCriteria($condition, $params); $command = $builder->createUpdateCommand($this->getTableSchema(), $attributes, $criteria); @@ -1722,7 +1744,7 @@ abstract class CActiveRecord extends CModel */ public function updateCounters($counters, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.updateCounters()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.updateCounters()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createCriteria($condition, $params); $command = $builder->createUpdateCounterCommand($this->getTableSchema(), $counters, $criteria); @@ -1739,7 +1761,7 @@ abstract class CActiveRecord extends CModel */ public function deleteByPk($pk, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.deleteByPk()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.deleteByPk()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createPkCriteria($this->getTableSchema(), $pk, $condition, $params); $command = $builder->createDeleteCommand($this->getTableSchema(), $criteria); @@ -1755,7 +1777,7 @@ abstract class CActiveRecord extends CModel */ public function deleteAll($condition = '', $params = array()) { - Yii::trace(get_class($this) . '.deleteAll()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.deleteAll()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $criteria = $builder->createCriteria($condition, $params); $command = $builder->createDeleteCommand($this->getTableSchema(), $criteria); @@ -1774,7 +1796,7 @@ abstract class CActiveRecord extends CModel */ public function deleteAllByAttributes($attributes, $condition = '', $params = array()) { - Yii::trace(get_class($this) . '.deleteAllByAttributes()', 'system.db.ar.CActiveRecord'); + Yii::trace(get_class($this) . '.deleteAllByAttributes()', 'system.db.ar.ActiveRecord'); $builder = $this->getCommandBuilder(); $table = $this->getTableSchema(); $criteria = $builder->createColumnCriteria($table, $attributes, $condition, $params); @@ -1788,7 +1810,7 @@ abstract class CActiveRecord extends CModel * @param array $attributes attribute values (column name=>column value) * @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated. * This parameter is added in version 1.0.3. - * @return CActiveRecord the newly created active record. The class of the object is the same as the model class. + * @return ActiveRecord the newly created active record. The class of the object is the same as the model class. * Null is returned if the input data is false. */ public function populateRecord($attributes, $callAfterFind = true) @@ -1850,7 +1872,7 @@ abstract class CActiveRecord extends CModel * For example, by creating a record based on the value of a column, * you may implement the so-called single-table inheritance mapping. * @param array $attributes list of attribute values for the active records. - * @return CActiveRecord the active record + * @return ActiveRecord the active record * @since 1.0.2 */ protected function instantiate($attributes) @@ -1877,7 +1899,7 @@ abstract class CActiveRecord extends CModel /** * CBaseActiveRelation is the base class for all active relations. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0.4 */ @@ -2020,7 +2042,7 @@ class CBaseActiveRelation extends CComponent /** * CStatRelation represents a statistical relational query. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0.4 */ @@ -2058,7 +2080,7 @@ class CStatRelation extends CBaseActiveRelation /** * CActiveRelation is the base class for representing active relations that bring back related objects. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ @@ -2081,7 +2103,7 @@ class CActiveRelation extends CBaseActiveRelation public $alias; /** * @var string|array specifies which related objects should be eagerly loaded when this related object is lazily loaded. - * For more details about this property, see {@link CActiveRecord::with()}. + * For more details about this property, see {@link ActiveRecord::with()}. */ public $with = array(); /** @@ -2155,7 +2177,7 @@ class CActiveRelation extends CBaseActiveRelation /** * CBelongsToRelation represents the parameters specifying a BELONGS_TO relation. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ @@ -2167,7 +2189,7 @@ class CBelongsToRelation extends CActiveRelation /** * CHasOneRelation represents the parameters specifying a HAS_ONE relation. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ @@ -2185,7 +2207,7 @@ class CHasOneRelation extends CActiveRelation /** * CHasManyRelation represents the parameters specifying a HAS_MANY relation. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ @@ -2238,7 +2260,7 @@ class CHasManyRelation extends CActiveRelation /** * CManyManyRelation represents the parameters specifying a MANY_MANY relation. * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ @@ -2248,14 +2270,14 @@ class CManyManyRelation extends CHasManyRelation /** - * CActiveRecordMetaData represents the meta-data for an Active Record class. + * ActiveRecordMetaData represents the meta-data for an Active Record class. * * @author Qiang Xue - * @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ + * @version $Id: ActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $ * @package system.db.ar * @since 1.0 */ -class CActiveRecordMetaData +class ActiveRecordMetaData { /** * @var CDbTableSchema the table schema information @@ -2278,7 +2300,7 @@ class CActiveRecordMetaData /** * Constructor. - * @param CActiveRecord $model the model instance + * @param ActiveRecord $model the model instance */ public function __construct($model) { diff --git a/framework/db/dao/Command.php b/framework/db/dao/Command.php new file mode 100644 index 0000000..92ff6a9 --- /dev/null +++ b/framework/db/dao/Command.php @@ -0,0 +1,1499 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CDbCommand represents an SQL statement to execute against a database. + * + * It is usually created by calling {@link CDbConnection::createCommand}. + * The SQL statement to be executed may be set via {@link setText Text}. + * + * To execute a non-query SQL (such as insert, delete, update), call + * {@link execute}. To execute an SQL statement that returns result data set + * (such as SELECT), use {@link query} or its convenient versions {@link queryRow}, + * {@link queryColumn}, or {@link queryScalar}. + * + * If an SQL statement returns results (such as a SELECT SQL), the results + * can be accessed via the returned {@link CDbDataReader}. + * + * CDbCommand supports SQL statment preparation and parameter binding. + * Call {@link bindParam} to bind a PHP variable to a parameter in SQL. + * Call {@link bindValue} to bind a value to an SQL parameter. + * When binding a parameter, the SQL statement is automatically prepared. + * You may also call {@link prepare} to explicitly prepare an SQL statement. + * + * Starting from version 1.1.6, CDbCommand can also be used as a query builder + * that builds a SQL statement from code fragments. For example, + *
+ * $user = Yii::app()->db->createCommand()
+ *     ->select('username, password')
+ *     ->from('tbl_user')
+ *     ->where('id=:id', array(':id'=>1))
+ *     ->queryRow();
+ * 
+ * + * @author Qiang Xue + * @version $Id: CDbCommand.php 3240 2011-05-25 19:22:47Z qiang.xue $ + * @package system.db + * @since 1.0 + */ +class CDbCommand extends CComponent +{ + /** + * @var array the parameters (name=>value) to be bound to the current query. + * @since 1.1.6 + */ + public $params = array(); + + private $_connection; + private $_text; + private $_statement; + private $_paramLog = array(); + private $_query; + private $_fetchMode = array(PDO::FETCH_ASSOC); + + /** + * Constructor. + * @param CDbConnection $connection the database connection + * @param mixed $query the DB query to be executed. This can be either + * a string representing a SQL statement, or an array whose name-value pairs + * will be used to set the corresponding properties of the created command object. + * + * For example, you can pass in either 'SELECT * FROM tbl_user' + * or array('select'=>'*', 'from'=>'tbl_user'). They are equivalent + * in terms of the final query result. + * + * When passing the query as an array, the following properties are commonly set: + * {@link select}, {@link distinct}, {@link from}, {@link where}, {@link join}, + * {@link group}, {@link having}, {@link order}, {@link limit}, {@link offset} and + * {@link union}. Please refer to the setter of each of these properties for details + * about valid property values. This feature has been available since version 1.1.6. + * + * Since 1.1.7 it is possible to use a specific mode of data fetching by setting + * {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php} + * for more details. + */ + public function __construct(CDbConnection $connection, $query = null) + { + $this->_connection = $connection; + if (is_array($query)) + { + foreach ($query as $name => $value) + $this->$name = $value; + } + else + $this->setText($query); + } + + /** + * Set the statement to null when serializing. + * @return array + */ + public function __sleep() + { + $this->_statement = null; + return array_keys(get_object_vars($this)); + } + + /** + * Set the default fetch mode for this statement + * @param mixed $mode fetch mode + * @return CDbCommand + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + * @since 1.1.7 + */ + public function setFetchMode($mode) + { + $params = func_get_args(); + $this->_fetchMode = $params; + return $this; + } + + /** + * Cleans up the command and prepares for building a new query. + * This method is mainly used when a command object is being reused + * multiple times for building different queries. + * Calling this method will clean up all internal states of the command object. + * @return CDbCommand this command instance + * @since 1.1.6 + */ + public function reset() + { + $this->_text = null; + $this->_query = null; + $this->_statement = null; + $this->_paramLog = array(); + $this->params = array(); + return $this; + } + + /** + * @return string the SQL statement to be executed + */ + public function getText() + { + if ($this->_text == '' && !empty($this->_query)) + $this->setText($this->buildQuery($this->_query)); + return $this->_text; + } + + /** + * Specifies the SQL statement to be executed. + * Any previous execution will be terminated or cancel. + * @param string $value the SQL statement to be executed + * @return CDbCommand this command instance + */ + public function setText($value) + { + if ($this->_connection->tablePrefix !== null && $value != '') + $this->_text = preg_replace('/{{(.*?)}}/', $this->_connection->tablePrefix . '\1', $value); + else + $this->_text = $value; + $this->cancel(); + return $this; + } + + /** + * @return CDbConnection the connection associated with this command + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return PDOStatement the underlying PDOStatement for this command + * It could be null if the statement is not prepared yet. + */ + public function getPdoStatement() + { + return $this->_statement; + } + + /** + * Prepares the SQL statement to be executed. + * For complex SQL statement that is to be executed multiple times, + * this may improve performance. + * For SQL statement with binding parameters, this method is invoked + * automatically. + */ + public function prepare() + { + if ($this->_statement == null) + { + try + { + $this->_statement = $this->getConnection()->getPdoInstance()->prepare($this->getText()); + $this->_paramLog = array(); + } + catch(Exception $e) + { + Yii::log('Error in preparing SQL: ' . $this->getText(), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); + $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; + throw new CDbException(Yii::t('yii', 'CDbCommand failed to prepare the SQL statement: {error}', + array('{error}' => $e->getMessage())), (int)$e->getCode(), $errorInfo); + } + } + } + + /** + * Cancels the execution of the SQL statement. + */ + public function cancel() + { + $this->_statement = null; + } + + /** + * Binds a parameter to the SQL statement to be executed. + * @param mixed $name Parameter identifier. For a prepared statement + * using named placeholders, this will be a parameter name of + * the form :name. For a prepared statement using question mark + * placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter + * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. + * @param integer $length length of the data type + * @param mixed $driverOptions the driver-specific options (this is available since version 1.1.6) + * @return CDbCommand the current command being executed (this is available since version 1.0.8) + * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php + */ + public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) + { + $this->prepare(); + if ($dataType === null) + $this->_statement->bindParam($name, $value, $this->_connection->getPdoType(gettype($value))); + elseif ($length === null) + $this->_statement->bindParam($name, $value, $dataType); + elseif ($driverOptions === null) + $this->_statement->bindParam($name, $value, $dataType, $length); + else + $this->_statement->bindParam($name, $value, $dataType, $length, $driverOptions); + $this->_paramLog[$name] =& $value; + return $this; + } + + /** + * Binds a value to a parameter. + * @param mixed $name Parameter identifier. For a prepared statement + * using named placeholders, this will be a parameter name of + * the form :name. For a prepared statement using question mark + * placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value The value to bind to the parameter + * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. + * @return CDbCommand the current command being executed (this is available since version 1.0.8) + * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php + */ + public function bindValue($name, $value, $dataType = null) + { + $this->prepare(); + if ($dataType === null) + $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); + else + $this->_statement->bindValue($name, $value, $dataType); + $this->_paramLog[$name] = $value; + return $this; + } + + /** + * Binds a list of values to the corresponding parameters. + * This is similar to {@link bindValue} except that it binds multiple values. + * Note that the SQL data type of each value is determined by its PHP type. + * @param array $values the values to be bound. This must be given in terms of an associative + * array with array keys being the parameter names, and array values the corresponding parameter values. + * For example, array(':name'=>'John', ':age'=>25). + * @return CDbCommand the current command being executed + * @since 1.1.5 + */ + public function bindValues($values) + { + $this->prepare(); + foreach ($values as $name => $value) + { + $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); + $this->_paramLog[$name] = $value; + } + return $this; + } + + /** + * Executes the SQL statement. + * This method is meant only for executing non-query SQL statement. + * No result set will be returned. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return integer number of rows affected by the execution. + * @throws CException execution failed + */ + public function execute($params = array()) + { + if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) + { + $p = array(); + foreach ($pars as $name => $value) + $p[$name] = $name . '=' . var_export($value, true); + $par = '. Bound with ' . implode(', ', $p); + } + else + $par = ''; + Yii::trace('Executing SQL: ' . $this->getText() . $par, 'system.db.CDbCommand'); + try + { + if ($this->_connection->enableProfiling) + Yii::beginProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); + + $this->prepare(); + if ($params === array()) + $this->_statement->execute(); + else + $this->_statement->execute($params); + $n = $this->_statement->rowCount(); + + if ($this->_connection->enableProfiling) + Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); + + return $n; + } + catch(Exception $e) + { + if ($this->_connection->enableProfiling) + Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); + $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; + $message = $e->getMessage(); + Yii::log(Yii::t('yii', 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.', + array('{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); + if (YII_DEBUG) + $message .= '. The SQL statement executed was: ' . $this->getText() . $par; + throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}', + array('{error}' => $message)), (int)$e->getCode(), $errorInfo); + } + } + + /** + * Executes the SQL statement and returns query result. + * This method is for executing an SQL query that returns result set. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return CDbDataReader the reader object for fetching the query result + * @throws CException execution failed + */ + public function query($params = array()) + { + return $this->queryInternal('', 0, $params); + } + + /** + * Executes the SQL statement and returns all rows. + * @param boolean $fetchAssociative whether each row should be returned as an associated array with + * column names as the keys or the array keys are column indexes (0-based). + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return array all rows of the query result. Each array element is an array representing a row. + * An empty array is returned if the query results in nothing. + * @throws CException execution failed + */ + public function queryAll($fetchAssociative = true, $params = array()) + { + return $this->queryInternal('fetchAll', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); + } + + /** + * Executes the SQL statement and returns the first row of the result. + * This is a convenient method of {@link query} when only the first row of data is needed. + * @param boolean $fetchAssociative whether the row should be returned as an associated array with + * column names as the keys or the array keys are column indexes (0-based). + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return mixed the first row (in terms of an array) of the query result, false if no result. + * @throws CException execution failed + */ + public function queryRow($fetchAssociative = true, $params = array()) + { + return $this->queryInternal('fetch', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); + } + + /** + * Executes the SQL statement and returns the value of the first column in the first row of data. + * This is a convenient method of {@link query} when only a single scalar + * value is needed (e.g. obtaining the count of the records). + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return mixed the value of the first column in the first row of the query result. False is returned if there is no value. + * @throws CException execution failed + */ + public function queryScalar($params = array()) + { + $result = $this->queryInternal('fetchColumn', 0, $params); + if (is_resource($result) && get_resource_type($result) === 'stream') + return stream_get_contents($result); + else + return $result; + } + + /** + * Executes the SQL statement and returns the first column of the result. + * This is a convenient method of {@link query} when only the first column of data is needed. + * Note, the column returned will contain the first element in each row of result. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that if you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return array the first column of the query result. Empty array if no result. + * @throws CException execution failed + */ + public function queryColumn($params = array()) + { + return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN, $params); + } + + /** + * @param string $method method of PDOStatement to be called + * @param mixed $mode parameters to be passed to the method + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing + * them in this way can improve the performance. Note that you pass parameters in this way, + * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. + * binding methods and the input parameters this way can improve the performance. + * This parameter has been available since version 1.0.10. + * @return mixed the method execution result + */ + private function queryInternal($method, $mode, $params = array()) + { + $params = array_merge($this->params, $params); + + if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) + { + $p = array(); + foreach ($pars as $name => $value) + $p[$name] = $name . '=' . var_export($value, true); + $par = '. Bound with ' . implode(', ', $p); + } + else + $par = ''; + + Yii::trace('Querying SQL: ' . $this->getText() . $par, 'system.db.CDbCommand'); + + if ($this->_connection->queryCachingCount > 0 && $method !== '' + && $this->_connection->queryCachingDuration > 0 + && $this->_connection->queryCacheID !== false + && ($cache = Yii::app()->getComponent($this->_connection->queryCacheID)) !== null) + { + $this->_connection->queryCachingCount--; + $cacheKey = 'yii:dbquery' . $this->_connection->connectionString . ':' . $this->_connection->username; + $cacheKey .= ':' . $this->getText() . ':' . serialize(array_merge($this->_paramLog, $params)); + if (($result = $cache->get($cacheKey)) !== false) + { + Yii::trace('Query result found in cache', 'system.db.CDbCommand'); + return $result; + } + } + + try + { + if ($this->_connection->enableProfiling) + Yii::beginProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); + + $this->prepare(); + if ($params === array()) + $this->_statement->execute(); + else + $this->_statement->execute($params); + + if ($method === '') + $result = new CDbDataReader($this); + else + { + $mode = (array)$mode; + $result = call_user_func_array(array($this->_statement, $method), $mode); + $this->_statement->closeCursor(); + } + + if ($this->_connection->enableProfiling) + Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); + + if (isset($cache, $cacheKey)) + $cache->set($cacheKey, $result, $this->_connection->queryCachingDuration, $this->_connection->queryCachingDependency); + + return $result; + } + catch(Exception $e) + { + if ($this->_connection->enableProfiling) + Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); + $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; + $message = $e->getMessage(); + Yii::log(Yii::t('yii', 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.', + array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); + if (YII_DEBUG) + $message .= '. The SQL statement executed was: ' . $this->getText() . $par; + throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}', + array('{error}' => $message)), (int)$e->getCode(), $errorInfo); + } + } + + /** + * Builds a SQL SELECT statement from the given query specification. + * @param array $query the query specification in name-value pairs. The following + * query options are supported: {@link select}, {@link distinct}, {@link from}, + * {@link where}, {@link join}, {@link group}, {@link having}, {@link order}, + * {@link limit}, {@link offset} and {@link union}. + * @return string the SQL statement + * @since 1.1.6 + */ + public function buildQuery($query) + { + $sql = isset($query['distinct']) && $query['distinct'] ? 'SELECT DISTINCT' : 'SELECT'; + $sql .= ' ' . (isset($query['select']) ? $query['select'] : '*'); + + if (isset($query['from'])) + $sql .= "\nFROM " . $query['from']; + else + throw new CDbException(Yii::t('yii', 'The DB query must contain the "from" portion.')); + + if (isset($query['join'])) + $sql .= "\n" . (is_array($query['join']) ? implode("\n", $query['join']) : $query['join']); + + if (isset($query['where'])) + $sql .= "\nWHERE " . $query['where']; + + if (isset($query['group'])) + $sql .= "\nGROUP BY " . $query['group']; + + if (isset($query['having'])) + $sql .= "\nHAVING " . $query['having']; + + if (isset($query['order'])) + $sql .= "\nORDER BY " . $query['order']; + + $limit = isset($query['limit']) ? (int)$query['limit'] : -1; + $offset = isset($query['offset']) ? (int)$query['offset'] : -1; + if ($limit >= 0 || $offset > 0) + $sql = $this->_connection->getCommandBuilder()->applyLimit($sql, $limit, $offset); + + if (isset($query['union'])) + $sql .= "\nUNION (\n" . (is_array($query['union']) ? implode("\n) UNION (\n", $query['union']) : $query['union']) . ')'; + + return $sql; + } + + /** + * Sets the SELECT part of the query. + * @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns. + * 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. This parameter is supported since version 1.1.8. + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function select($columns = '*', $option = '') + { + if (is_string($columns) && strpos($columns, '(') !== false) + $this->_query['select'] = $columns; + else + { + if (!is_array($columns)) + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + + foreach ($columns as $i => $column) + { + if (is_object($column)) + $columns[$i] = (string)$column; + elseif (strpos($column, '(') === false) + { + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $column, $matches)) + $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' AS ' . $this->_connection->quoteColumnName($matches[2]); + else + $columns[$i] = $this->_connection->quoteColumnName($column); + } + } + $this->_query['select'] = implode(', ', $columns); + } + if ($option != '') + $this->_query['select'] = $option . ' ' . $this->_query['select']; + return $this; + } + + /** + * Returns the SELECT part in the query. + * @return string the SELECT part (without 'SELECT') in the query. + * @since 1.1.6 + */ + public function getSelect() + { + return isset($this->_query['select']) ? $this->_query['select'] : ''; + } + + /** + * Sets the SELECT part in the query. + * @param mixed $value the data to be selected. Please refer to {@link select()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setSelect($value) + { + $this->select($value); + } + + /** + * Sets the SELECT part of the query with the DISTINCT flag turned on. + * This is the same as {@link select} except that the DISTINCT flag is turned on. + * @param mixed $columns the columns to be selected. See {@link select} for more details. + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function selectDistinct($columns = '*') + { + $this->_query['distinct'] = true; + return $this->select($columns); + } + + /** + * Returns a value indicating whether SELECT DISTINCT should be used. + * @return boolean a value indicating whether SELECT DISTINCT should be used. + * @since 1.1.6 + */ + public function getDistinct() + { + return isset($this->_query['distinct']) ? $this->_query['distinct'] : false; + } + + /** + * Sets a value indicating whether SELECT DISTINCT should be used. + * @param boolean $value a value indicating whether SELECT DISTINCT should be used. + * @since 1.1.6 + */ + public function setDistinct($value) + { + $this->_query['distinct'] = $value; + } + + /** + * Sets the FROM part of the query. + * @param mixed $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 CDbCommand the command object itself + * @since 1.1.6 + */ + public function from($tables) + { + if (is_string($tables) && strpos($tables, '(') !== false) + $this->_query['from'] = $tables; + else + { + if (!is_array($tables)) + $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY); + foreach ($tables as $i => $table) + { + if (strpos($table, '(') === false) + { + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias + $tables[$i] = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); + else + $tables[$i] = $this->_connection->quoteTableName($table); + } + } + $this->_query['from'] = implode(', ', $tables); + } + return $this; + } + + /** + * Returns the FROM part in the query. + * @return string the FROM part (without 'FROM' ) in the query. + * @since 1.1.6 + */ + public function getFrom() + { + return isset($this->_query['from']) ? $this->_query['from'] : ''; + } + + /** + * Sets the FROM part in the query. + * @param mixed $value the tables to be selected from. Please refer to {@link from()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setFrom($value) + { + $this->from($value); + } + + /** + * Sets the WHERE part of the query. + * + * The method requires a $conditions parameter, and optionally a $params parameter + * specifying the values to be bound to the query. + * + * The $conditions parameter should be either a string (e.g. 'id=1') or an array. + * If the latter, it must be of the format array(operator, operand1, operand2, ...), + * where the operator can be one of the followings, and the possible operands depend on the corresponding + * operator: + *
    + *
  • 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 same 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 as the and operator except that the operands are concatenated using OR.
  • + *
  • 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 as 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.
  • + *
  • not like: similar as the like operator except that LIKE is replaced with NOT LIKE in the generated condition.
  • + *
  • or like: similar as the like operator except that OR is used to concatenated the LIKE predicates.
  • + *
  • or not like: similar as the not like operator except that OR is used to concatenated the NOT LIKE predicates.
  • + *
+ * @param mixed $conditions the conditions that should be put in the WHERE part. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function where($conditions, $params = array()) + { + $this->_query['where'] = $this->processConditions($conditions); + foreach ($params as $name => $value) + $this->params[$name] = $value; + return $this; + } + + /** + * Returns the WHERE part in the query. + * @return string the WHERE part (without 'WHERE' ) in the query. + * @since 1.1.6 + */ + public function getWhere() + { + return isset($this->_query['where']) ? $this->_query['where'] : ''; + } + + /** + * Sets the WHERE part in the query. + * @param mixed $value the where part. Please refer to {@link where()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setWhere($value) + { + $this->where($value); + } + + /** + * 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 mixed $conditions the join condition that should appear in the ON part. + * Please refer to {@link where} on how to specify conditions. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function join($table, $conditions, $params = array()) + { + return $this->joinInternal('join', $table, $conditions, $params); + } + + /** + * Returns the join part in the query. + * @return mixed the join part in the query. This can be an array representing + * multiple join fragments, or a string representing a single jojin fragment. + * Each join fragment will contain the proper join operator (e.g. LEFT JOIN). + * @since 1.1.6 + */ + public function getJoin() + { + return isset($this->_query['join']) ? $this->_query['join'] : ''; + } + + /** + * Sets the join part in the query. + * @param mixed $value the join part in the query. This can be either a string or + * an array representing multiple join parts in the query. Each part must contain + * the proper join operator (e.g. 'LEFT JOIN tbl_profile ON tbl_user.id=tbl_profile.id') + * @since 1.1.6 + */ + public function setJoin($value) + { + $this->_query['join'] = $value; + } + + /** + * 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 mixed $conditions the join condition that should appear in the ON part. + * Please refer to {@link where} on how to specify conditions. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function leftJoin($table, $conditions, $params = array()) + { + return $this->joinInternal('left join', $table, $conditions, $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 mixed $conditions the join condition that should appear in the ON part. + * Please refer to {@link where} on how to specify conditions. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function rightJoin($table, $conditions, $params = array()) + { + return $this->joinInternal('right join', $table, $conditions, $params); + } + + /** + * Appends a CROSS JOIN part to the query. + * Note that not all DBMS support CROSS 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). + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function crossJoin($table) + { + return $this->joinInternal('cross join', $table); + } + + /** + * Appends a NATURAL JOIN part to the query. + * Note that not all DBMS support NATURAL 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). + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function naturalJoin($table) + { + return $this->joinInternal('natural join', $table); + } + + /** + * Sets the GROUP BY part of the query. + * @param mixed $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 CDbCommand the command object itself + * @since 1.1.6 + */ + public function group($columns) + { + if (is_string($columns) && strpos($columns, '(') !== false) + $this->_query['group'] = $columns; + else + { + if (!is_array($columns)) + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + foreach ($columns as $i => $column) + { + if (is_object($column)) + $columns[$i] = (string)$column; + elseif (strpos($column, '(') === false) + $columns[$i] = $this->_connection->quoteColumnName($column); + } + $this->_query['group'] = implode(', ', $columns); + } + return $this; + } + + /** + * Returns the GROUP BY part in the query. + * @return string the GROUP BY part (without 'GROUP BY' ) in the query. + * @since 1.1.6 + */ + public function getGroup() + { + return isset($this->_query['group']) ? $this->_query['group'] : ''; + } + + /** + * Sets the GROUP BY part in the query. + * @param mixed $value the GROUP BY part. Please refer to {@link group()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setGroup($value) + { + $this->group($value); + } + + /** + * Sets the HAVING part of the query. + * @param mixed $conditions the conditions to be put after HAVING. + * Please refer to {@link where} on how to specify conditions. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function having($conditions, $params = array()) + { + $this->_query['having'] = $this->processConditions($conditions); + foreach ($params as $name => $value) + $this->params[$name] = $value; + return $this; + } + + /** + * Returns the HAVING part in the query. + * @return string the HAVING part (without 'HAVING' ) in the query. + * @since 1.1.6 + */ + public function getHaving() + { + return isset($this->_query['having']) ? $this->_query['having'] : ''; + } + + /** + * Sets the HAVING part in the query. + * @param mixed $value the HAVING part. Please refer to {@link having()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setHaving($value) + { + $this->having($value); + } + + /** + * Sets the ORDER BY part of the query. + * @param mixed $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 CDbCommand the command object itself + * @since 1.1.6 + */ + public function order($columns) + { + if (is_string($columns) && strpos($columns, '(') !== false) + $this->_query['order'] = $columns; + else + { + if (!is_array($columns)) + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + foreach ($columns as $i => $column) + { + if (is_object($column)) + $columns[$i] = (string)$column; + elseif (strpos($column, '(') === false) + { + if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) + $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' ' . strtoupper($matches[2]); + else + $columns[$i] = $this->_connection->quoteColumnName($column); + } + } + $this->_query['order'] = implode(', ', $columns); + } + return $this; + } + + /** + * Returns the ORDER BY part in the query. + * @return string the ORDER BY part (without 'ORDER BY' ) in the query. + * @since 1.1.6 + */ + public function getOrder() + { + return isset($this->_query['order']) ? $this->_query['order'] : ''; + } + + /** + * Sets the ORDER BY part in the query. + * @param mixed $value the ORDER BY part. Please refer to {@link order()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setOrder($value) + { + $this->order($value); + } + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit + * @param integer $offset the offset + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function limit($limit, $offset = null) + { + $this->_query['limit'] = (int)$limit; + if ($offset !== null) + $this->offset($offset); + return $this; + } + + /** + * Returns the LIMIT part in the query. + * @return string the LIMIT part (without 'LIMIT' ) in the query. + * @since 1.1.6 + */ + public function getLimit() + { + return isset($this->_query['limit']) ? $this->_query['limit'] : -1; + } + + /** + * Sets the LIMIT part in the query. + * @param integer $value the LIMIT part. Please refer to {@link limit()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setLimit($value) + { + $this->limit($value); + } + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function offset($offset) + { + $this->_query['offset'] = (int)$offset; + return $this; + } + + /** + * Returns the OFFSET part in the query. + * @return string the OFFSET part (without 'OFFSET' ) in the query. + * @since 1.1.6 + */ + public function getOffset() + { + return isset($this->_query['offset']) ? $this->_query['offset'] : -1; + } + + /** + * Sets the OFFSET part in the query. + * @param integer $value the OFFSET part. Please refer to {@link offset()} for details + * on how to specify this parameter. + * @since 1.1.6 + */ + public function setOffset($value) + { + $this->offset($value); + } + + /** + * Appends a SQL statement using UNION operator. + * @param string $sql the SQL statement to be appended using UNION + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + public function union($sql) + { + if (isset($this->_query['union']) && is_string($this->_query['union'])) + $this->_query['union'] = array($this->_query['union']); + + $this->_query['union'][] = $sql; + + return $this; + } + + /** + * Returns the UNION part in the query. + * @return mixed the UNION part (without 'UNION' ) in the query. + * This can be either a string or an array representing multiple union parts. + * @since 1.1.6 + */ + public function getUnion() + { + return isset($this->_query['union']) ? $this->_query['union'] : ''; + } + + /** + * Sets the UNION part in the query. + * @param mixed $value the UNION part. This can be either a string or an array + * representing multiple SQL statements to be unioned together. + * @since 1.1.6 + */ + public function setUnion($value) + { + $this->_query['union'] = $value; + } + + /** + * Creates and executes an INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column data (name=>value) to be inserted into the table. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function insert($table, $columns) + { + $params = array(); + $names = array(); + $placeholders = array(); + foreach ($columns as $name => $value) + { + $names[] = $this->_connection->quoteColumnName($name); + if ($value instanceof CDbExpression) + { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) + $params[$n] = $v; + } + else + { + $placeholders[] = ':' . $name; + $params[':' . $name] = $value; + } + } + $sql = 'INSERT INTO ' . $this->_connection->quoteTableName($table) + . ' (' . implode(', ', $names) . ') VALUES (' + . implode(', ', $placeholders) . ')'; + return $this->setText($sql)->execute($params); + } + + /** + * Creates and executes an UPDATE SQL statement. + * The method will properly escape the column names and bind the values to be updated. + * @param string $table the table to be updated. + * @param array $columns the column data (name=>value) to be updated. + * @param mixed $conditions the conditions that will be put in the WHERE part. Please + * refer to {@link where} on how to specify conditions. + * @param array $params the parameters to be bound to the query. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function update($table, $columns, $conditions = '', $params = array()) + { + $lines = array(); + foreach ($columns as $name => $value) + { + if ($value instanceof CDbExpression) + { + $lines[] = $this->_connection->quoteColumnName($name) . '=' . $value->expression; + foreach ($value->params as $n => $v) + $params[$n] = $v; + } + else + { + $lines[] = $this->_connection->quoteColumnName($name) . '=:' . $name; + $params[':' . $name] = $value; + } + } + $sql = 'UPDATE ' . $this->_connection->quoteTableName($table) . ' SET ' . implode(', ', $lines); + if (($where = $this->processConditions($conditions)) != '') + $sql .= ' WHERE ' . $where; + return $this->setText($sql)->execute($params); + } + + /** + * Creates and executes a DELETE SQL statement. + * @param string $table the table where the data will be deleted from. + * @param mixed $conditions the conditions that will be put in the WHERE part. Please + * refer to {@link where} on how to specify conditions. + * @param array $params the parameters to be bound to the query. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function delete($table, $conditions = '', $params = array()) + { + $sql = 'DELETE FROM ' . $this->_connection->quoteTableName($table); + if (($where = $this->processConditions($conditions)) != '') + $sql .= ' WHERE ' . $where; + return $this->setText($sql)->execute($params); + } + + /** + * Builds and executes a SQL statement for creating a new DB table. + * + * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), + * where name stands for a column name which will be properly quoted by the method, and definition + * stands for the column type which can contain an abstract DB type. + * The {@link getColumnType} method will be invoked to convert any abstract type into a physical one. + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly + * inserted into the generated SQL. + * + * @param string $table the name of the table to be created. The name will be properly quoted by the method. + * @param array $columns the columns (name=>definition) in the new table. + * @param string $options additional SQL fragment that will be appended to the generated SQL. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function createTable($table, $columns, $options = null) + { + return $this->setText($this->getConnection()->getSchema()->createTable($table, $columns, $options))->execute(); + } + + /** + * Builds and executes a SQL statement for renaming a DB table. + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function renameTable($table, $newName) + { + return $this->setText($this->getConnection()->getSchema()->renameTable($table, $newName))->execute(); + } + + /** + * Builds and executes a SQL statement for dropping a DB table. + * @param string $table the table to be dropped. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function dropTable($table) + { + return $this->setText($this->getConnection()->getSchema()->dropTable($table))->execute(); + } + + /** + * Builds and executes a SQL statement for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function truncateTable($table) + { + $schema = $this->getConnection()->getSchema(); + $n = $this->setText($schema->truncateTable($table))->execute(); + if (strncasecmp($this->getConnection()->getDriverName(), 'sqlite', 6) === 0) + $schema->resetSequence($schema->getTable($table)); + return $n; + } + + /** + * Builds and executes a SQL statement for adding a new DB column. + * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. + * @param string $column the name of the new column. The name will be properly quoted by the method. + * @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function addColumn($table, $column, $type) + { + return $this->setText($this->getConnection()->getSchema()->addColumn($table, $column, $type))->execute(); + } + + /** + * Builds and executes a SQL statement for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function dropColumn($table, $column) + { + return $this->setText($this->getConnection()->getSchema()->dropColumn($table, $column))->execute(); + } + + /** + * Builds and executes a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $name the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function renameColumn($table, $name, $newName) + { + return $this->setText($this->getConnection()->getSchema()->renameColumn($table, $name, $newName))->execute(); + } + + /** + * Builds and executes a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function alterColumn($table, $column, $type) + { + return $this->setText($this->getConnection()->getSchema()->alterColumn($table, $column, $type))->execute(); + } + + /** + * Builds a SQL statement for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string $refTable the table that the foreign key references to. + * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + return $this->setText($this->getConnection()->getSchema()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update))->execute(); + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function dropForeignKey($name, $table) + { + return $this->setText($this->getConnection()->getSchema()->dropForeignKey($name, $table))->execute(); + } + + /** + * Builds and executes a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas. The column names will be properly quoted by the method. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function createIndex($name, $table, $column, $unique = false) + { + return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute(); + } + + /** + * Builds and executes a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function dropIndex($name, $table) + { + return $this->setText($this->getConnection()->getSchema()->dropIndex($name, $table))->execute(); + } + + /** + * Generates the condition string that will be put in the WHERE part + * @param mixed $conditions the conditions that will be put in the WHERE part. + * @return string the condition string to put in the WHERE part + */ + private function processConditions($conditions) + { + if (!is_array($conditions)) + return $conditions; + elseif ($conditions === array()) + return ''; + $n = count($conditions); + $operator = strtoupper($conditions[0]); + if ($operator === 'OR' || $operator === 'AND') + { + $parts = array(); + for ($i = 1;$i < $n;++$i) + { + $condition = $this->processConditions($conditions[$i]); + if ($condition !== '') + $parts[] = '(' . $condition . ')'; + } + return $parts === array() ? '' : implode(' ' . $operator . ' ', $parts); + } + + if (!isset($conditions[1], $conditions[2])) + return ''; + + $column = $conditions[1]; + if (strpos($column, '(') === false) + $column = $this->_connection->quoteColumnName($column); + + $values = $conditions[2]; + if (!is_array($values)) + $values = array($values); + + if ($operator === 'IN' || $operator === 'NOT IN') + { + if ($values === array()) + return $operator === 'IN' ? '0=1' : ''; + foreach ($values as $i => $value) + { + if (is_string($value)) + $values[$i] = $this->_connection->quoteValue($value); + else + $values[$i] = (string)$value; + } + return $column . ' ' . $operator . ' (' . implode(', ', $values) . ')'; + } + + if ($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE') + { + if ($values === array()) + return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : ''; + + if ($operator === 'LIKE' || $operator === 'NOT LIKE') + $andor = ' AND '; + else + { + $andor = ' OR '; + $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; + } + $expressions = array(); + foreach ($values as $value) + $expressions[] = $column . ' ' . $operator . ' ' . $this->_connection->quoteValue($value); + return implode($andor, $expressions); + } + + throw new CDbException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}' => $operator))); + } + + /** + * Appends an JOIN part to the query. + * @param string $type the join type ('join', 'left join', 'right join', 'cross join', 'natural 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 mixed $conditions the join condition that should appear in the ON part. + * Please refer to {@link where} on how to specify conditions. + * @param array $params the parameters (name=>value) to be bound to the query + * @return CDbCommand the command object itself + * @since 1.1.6 + */ + private function joinInternal($type, $table, $conditions = '', $params = array()) + { + if (strpos($table, '(') === false) + { + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias + $table = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); + else + $table = $this->_connection->quoteTableName($table); + } + + $conditions = $this->processConditions($conditions); + if ($conditions != '') + $conditions = ' ON ' . $conditions; + + if (isset($this->_query['join']) && is_string($this->_query['join'])) + $this->_query['join'] = array($this->_query['join']); + + $this->_query['join'][] = strtoupper($type) . ' ' . $table . $conditions; + + foreach ($params as $name => $value) + $this->params[$name] = $value; + return $this; + } +} diff --git a/framework/db/dao/Connection.php b/framework/db/dao/Connection.php new file mode 100644 index 0000000..6e0db3f --- /dev/null +++ b/framework/db/dao/Connection.php @@ -0,0 +1,794 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db\dao; + +/** + * Connection represents a connection to a database. + * + * Connection works together with {@link CDbCommand}, {@link CDbDataReader} + * and {@link CDbTransaction} to provide data access to various DBMS + * in a common set of APIs. They are a thin wrapper of the {@link http://www.php.net/manual/en/ref.pdo.php PDO} + * PHP extension. + * + * To establish a connection, set {@link setActive active} to true after + * specifying {@link connectionString}, {@link username} and {@link password}. + * + * The following example shows how to create a Connection instance and establish + * the actual connection: + *
+ * $connection=new Connection($dsn,$username,$password);
+ * $connection->active=true;
+ * 
+ * + * After the DB connection is established, one can execute an SQL statement like the following: + *
+ * $command=$connection->createCommand($sqlStatement);
+ * $command->execute();   // a non-query SQL statement execution
+ * // or execute an SQL query and fetch the result set
+ * $reader=$command->query();
+ *
+ * // each $row is an array representing a row of data
+ * foreach($reader as $row) ...
+ * 
+ * + * One can do prepared SQL execution and bind parameters to the prepared SQL: + *
+ * $command=$connection->createCommand($sqlStatement);
+ * $command->bindParam($name1,$value1);
+ * $command->bindParam($name2,$value2);
+ * $command->execute();
+ * 
+ * + * To use transaction, do like the following: + *
+ * $transaction=$connection->beginTransaction();
+ * try
+ * {
+ *    $connection->createCommand($sql1)->execute();
+ *    $connection->createCommand($sql2)->execute();
+ *    //.... other SQL executions
+ *    $transaction->commit();
+ * }
+ * catch(Exception $e)
+ * {
+ *    $transaction->rollBack();
+ * }
+ * 
+ * + * Connection also provides a set of methods to support setting and querying + * of certain DBMS attributes, such as {@link getNullConversion nullConversion}. + * + * Since Connection implements the interface IApplicationComponent, it can + * be used as an application component and be configured in application configuration, + * like the following, + *
+ * array(
+ *     'components'=>array(
+ *         'db'=>array(
+ *             'class'=>'Connection',
+ *             'connectionString'=>'sqlite:path/to/dbfile',
+ *         ),
+ *     ),
+ * )
+ * 
+ * + * @author Qiang Xue + * @since 2.0 + */ +class Connection extends \yii\base\ApplicationComponent +{ + /** + * @var string The Data Source Name, or DSN, contains the information required to connect to the database. + * @see http://www.php.net/manual/en/function.PDO-construct.php + * + * Note that if your database is using GBK or BIG5 charset, we highly recommend you + * to upgrade to PHP 5.3.6+ and specify charset via DSN like the following to prevent + * from hacking: `mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;`. + */ + public $dsn; + /** + * @var string the username for establishing DB connection. Defaults to empty string. + */ + public $username = ''; + /** + * @var string the password for establishing DB connection. Defaults to empty string. + */ + public $password = ''; + /** + * @var integer number of seconds that table metadata can remain valid in cache. + * Use 0 or negative value to indicate not caching schema. + * If greater than 0 and the primary cache is enabled, the table metadata will be cached. + * @see schemaCachingExclude + */ + public $schemaCachingDuration = 0; + /** + * @var array list of tables whose metadata should NOT be cached. Defaults to empty array. + * @see schemaCachingDuration + */ + public $schemaCachingExclude = array(); + /** + * @var string the ID of the cache application component that is used to cache the table metadata. + * Defaults to 'cache' which refers to the primary cache application component. + * Set this property to false if you want to disable caching table metadata. + * @since 1.0.10 + */ + public $schemaCacheID = 'cache'; + /** + * @var integer number of seconds that query results can remain valid in cache. + * Use 0 or negative value to indicate not caching query results (the default behavior). + * + * In order to enable query caching, this property must be a positive + * integer and {@link queryCacheID} must point to a valid cache component ID. + * + * The method {@link cache()} is provided as a convenient way of setting this property + * and {@link queryCachingDependency} on the fly. + * + * @see cache + * @see queryCachingDependency + * @see queryCacheID + * @since 1.1.7 + */ + public $queryCachingDuration = 0; + /** + * @var CCacheDependency the dependency that will be used when saving query results into cache. + * @see queryCachingDuration + * @since 1.1.7 + */ + public $queryCachingDependency; + /** + * @var integer the number of SQL statements that need to be cached next. + * If this is 0, then even if query caching is enabled, no query will be cached. + * Note that each time after executing a SQL statement (whether executed on DB server or fetched from + * query cache), this property will be reduced by 1 until 0. + * @since 1.1.7 + */ + public $queryCachingCount = 0; + /** + * @var string the ID of the cache application component that is used for query caching. + * Defaults to 'cache' which refers to the primary cache application component. + * Set this property to false if you want to disable query caching. + * @since 1.1.7 + */ + public $queryCacheID = 'cache'; + /** + * @var boolean whether the database connection should be automatically established + * the component is being initialized. Defaults to true. Note, this property is only + * effective when the Connection object is used as an application component. + */ + public $autoConnect = true; + /** + * @var string the charset used for database connection. The property is only used + * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset + * as specified by the database. + * + * Note that if you're using GBK or BIG5 then it's highly recommended to + * update to PHP 5.3.6+ and to specify charset via DSN like + * 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. + */ + public $charset; + /** + * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO + * will use the native prepare support if available. For some databases (such as MySQL), + * this may need to be set true so that PDO can emulate the prepare support to bypass + * the buggy native prepare support. Note, this property is only effective for PHP 5.1.3 or above. + * The default value is null, which will not change the ATTR_EMULATE_PREPARES value of PDO. + */ + public $emulatePrepare; + /** + * @var boolean whether to log the values that are bound to a prepare SQL statement. + * Defaults to false. During development, you may consider setting this property to true + * so that parameter values bound to SQL statements are logged for debugging purpose. + * You should be aware that logging parameter values could be expensive and have significant + * impact on the performance of your application. + * @since 1.0.5 + */ + public $enableParamLogging = false; + /** + * @var boolean whether to enable profiling the SQL statements being executed. + * Defaults to false. This should be mainly enabled and used during development + * to find out the bottleneck of SQL executions. + * @since 1.0.6 + */ + public $enableProfiling = false; + /** + * @var string the default prefix for table names. Defaults to null, meaning no table prefix. + * By setting this property, any token like '{{tableName}}' in {@link CDbCommand::text} will + * be replaced by 'prefixTableName', where 'prefix' refers to this property value. + * @since 1.1.0 + */ + public $tablePrefix; + /** + * @var array list of SQL statements that should be executed right after the DB connection is established. + * @since 1.1.1 + */ + public $initSQLs; + /** + * @var array mapping between PDO driver and schema class name. + * A schema class can be specified using path alias. + * @since 1.1.6 + */ + public $driverMap = array( + 'pgsql' => 'CPgsqlSchema', // PostgreSQL + 'mysqli' => 'CMysqlSchema', // MySQL + 'mysql' => 'CMysqlSchema', // MySQL + 'sqlite' => 'CSqliteSchema', // sqlite 3 + 'sqlite2' => 'CSqliteSchema', // sqlite 2 + 'mssql' => 'CMssqlSchema', // Mssql driver on windows hosts + 'dblib' => 'CMssqlSchema', // dblib drivers on linux (and maybe others os) hosts + 'sqlsrv' => 'CMssqlSchema', // Mssql + 'oci' => 'COciSchema', // Oracle driver + ); + + /** + * @var string Custom PDO wrapper class. + * @since 1.1.8 + */ + public $pdoClass = 'PDO'; + + private $_attributes = array(); + private $_active = false; + private $_pdo; + private $_transaction; + private $_schema; + + + /** + * Constructor. + * Note, the DB connection is not established when this connection + * instance is created. Set {@link setActive active} property to true + * to establish the connection. + * @param string $dsn The Data Source Name, or DSN, contains the information required to connect to the database. + * @param string $username The user name for the DSN string. + * @param string $password The password for the DSN string. + * @see http://www.php.net/manual/en/function.PDO-construct.php + */ + public function __construct($dsn = '', $username = '', $password = '') + { + $this->connectionString = $dsn; + $this->username = $username; + $this->password = $password; + } + + /** + * Close the connection when serializing. + * @return array + */ + public function __sleep() + { + $this->close(); + return array_keys(get_object_vars($this)); + } + + /** + * Returns a list of available PDO drivers. + * @return array list of available PDO drivers + * @see http://www.php.net/manual/en/function.PDO-getAvailableDrivers.php + */ + public static function getAvailableDrivers() + { + return PDO::getAvailableDrivers(); + } + + /** + * Initializes the component. + * This method is required by {@link IApplicationComponent} and is invoked by application + * when the Connection is used as an application component. + * If you override this method, make sure to call the parent implementation + * so that the component can be marked as initialized. + */ + public function init() + { + parent::init(); + if ($this->autoConnect) + $this->setActive(true); + } + + /** + * Returns whether the DB connection is established. + * @return boolean whether the DB connection is established + */ + public function getActive() + { + return $this->_active; + } + + /** + * Open or close the DB connection. + * @param boolean $value whether to open or close DB connection + * @throws CException if connection fails + */ + public function setActive($value) + { + if ($value != $this->_active) + { + if ($value) + $this->open(); + else + $this->close(); + } + } + + /** + * Sets the parameters about query caching. + * This method can be used to enable or disable query caching. + * By setting the $duration parameter to be 0, the query caching will be disabled. + * Otherwise, query results of the new SQL statements executed next will be saved in cache + * and remain valid for the specified duration. + * If the same query is executed again, the result may be fetched from cache directly + * without actually executing the SQL statement. + * @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 Connection the connection instance itself. + * @since 1.1.7 + */ + public function cache($duration, $dependency = null, $queryCount = 1) + { + $this->queryCachingDuration = $duration; + $this->queryCachingDependency = $dependency; + $this->queryCachingCount = $queryCount; + return $this; + } + + /** + * Opens DB connection if it is currently not + * @throws CException if connection fails + */ + protected function open() + { + if ($this->_pdo === null) + { + if (empty($this->connectionString)) + throw new CDbException(Yii::t('yii', 'Connection.connectionString cannot be empty.')); + try + { + Yii::trace('Opening DB connection', 'system.db.Connection'); + $this->_pdo = $this->createPdoInstance(); + $this->initConnection($this->_pdo); + $this->_active = true; + } + catch(PDOException $e) + { + if (YII_DEBUG) + { + throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection: {error}', + array('{error}' => $e->getMessage())), (int)$e->getCode(), $e->errorInfo); + } + else + { + Yii::log($e->getMessage(), CLogger::LEVEL_ERROR, 'exception.CDbException'); + throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection.'), (int)$e->getCode(), $e->errorInfo); + } + } + } + } + + /** + * Closes the currently active DB connection. + * It does nothing if the connection is already closed. + */ + protected function close() + { + Yii::trace('Closing DB connection', 'system.db.Connection'); + $this->_pdo = null; + $this->_active = false; + $this->_schema = null; + } + + /** + * Creates the PDO instance. + * When some functionalities are missing in the pdo driver, we may use + * an adapter class to provides them. + * @return PDO the pdo instance + * @since 1.0.4 + */ + protected function createPdoInstance() + { + $pdoClass = $this->pdoClass; + if (($pos = strpos($this->connectionString, ':')) !== false) + { + $driver = strtolower(substr($this->connectionString, 0, $pos)); + if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') + $pdoClass = 'CMssqlPdoAdapter'; + } + return new $pdoClass($this->connectionString, $this->username, + $this->password, $this->_attributes); + } + + /** + * Initializes the open db connection. + * This method is invoked right after the db connection is established. + * The default implementation is to set the charset for MySQL and PostgreSQL database connections. + * @param PDO $pdo the PDO instance + */ + protected function initConnection($pdo) + { + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) + $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); + if ($this->charset !== null) + { + $driver = strtolower($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)); + if (in_array($driver, array('pgsql', 'mysql', 'mysqli'))) + $pdo->exec('SET NAMES ' . $pdo->quote($this->charset)); + } + if ($this->initSQLs !== null) + { + foreach ($this->initSQLs as $sql) + $pdo->exec($sql); + } + } + + /** + * Returns the PDO instance. + * @return PDO the PDO instance, null if the connection is not established yet + */ + public function getPdoInstance() + { + return $this->_pdo; + } + + /** + * Creates a command for execution. + * @param mixed $query the DB query to be executed. This can be either a string representing a SQL statement, + * or an array representing different fragments of a SQL statement. Please refer to {@link CDbCommand::__construct} + * for more details about how to pass an array as the query. If this parameter is not given, + * you will have to call query builder methods of {@link CDbCommand} to build the DB query. + * @return CDbCommand the DB command + */ + public function createCommand($query = null) + { + $this->setActive(true); + return new CDbCommand($this, $query); + } + + /** + * Returns the currently active transaction. + * @return CDbTransaction the currently active transaction. Null if no active transaction. + */ + public function getCurrentTransaction() + { + if ($this->_transaction !== null) + { + if ($this->_transaction->getActive()) + return $this->_transaction; + } + return null; + } + + /** + * Starts a transaction. + * @return CDbTransaction the transaction initiated + */ + public function beginTransaction() + { + Yii::trace('Starting transaction', 'system.db.Connection'); + $this->setActive(true); + $this->_pdo->beginTransaction(); + return $this->_transaction = new CDbTransaction($this); + } + + /** + * Returns the database schema for the current connection + * @return CDbSchema the database schema for the current connection + */ + public function getSchema() + { + if ($this->_schema !== null) + return $this->_schema; + else + { + $driver = $this->getDriverName(); + if (isset($this->driverMap[$driver])) + return $this->_schema = Yii::createComponent($this->driverMap[$driver], $this); + else + throw new CDbException(Yii::t('yii', 'Connection does not support reading schema for {driver} database.', + array('{driver}' => $driver))); + } + } + + /** + * Returns the SQL command builder for the current DB connection. + * @return CDbCommandBuilder the command builder + * @since 1.0.4 + */ + public function getCommandBuilder() + { + return $this->getSchema()->getCommandBuilder(); + } + + /** + * Returns the ID of the last inserted row or sequence value. + * @param string $sequenceName name of the sequence object (required by some DBMS) + * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object + * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php + */ + public function getLastInsertID($sequenceName = '') + { + $this->setActive(true); + return $this->_pdo->lastInsertId($sequenceName); + } + + /** + * Quotes a string value for use in a query. + * @param string $str string to be quoted + * @return string the properly quoted string + * @see http://www.php.net/manual/en/function.PDO-quote.php + */ + public function quoteValue($str) + { + if (is_int($str) || is_float($str)) + return $str; + + $this->setActive(true); + if (($value = $this->_pdo->quote($str)) !== false) + return $value; + else // the driver doesn't support quote (e.g. oci) + return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; + } + + /** + * Quotes a table name for use in a query. + * If the table name contains schema prefix, the prefix will also be properly quoted. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteTableName($name) + { + return $this->getSchema()->quoteTableName($name); + } + + /** + * Quotes a column name for use in a query. + * If the column name contains prefix, the prefix will also be properly quoted. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteColumnName($name) + { + return $this->getSchema()->quoteColumnName($name); + } + + /** + * Determines the PDO type for the specified PHP type. + * @param string $type The PHP type (obtained by gettype() call). + * @return integer the corresponding PDO type + */ + public function getPdoType($type) + { + static $map = array + ( + 'boolean' => PDO::PARAM_BOOL, + 'integer' => PDO::PARAM_INT, + 'string' => PDO::PARAM_STR, + 'NULL' => PDO::PARAM_NULL, + ); + return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR; + } + + /** + * Returns the case of the column names + * @return mixed the case of the column names + * @see http://www.php.net/manual/en/pdo.setattribute.php + */ + public function getColumnCase() + { + return $this->getAttribute(PDO::ATTR_CASE); + } + + /** + * Sets the case of the column names. + * @param mixed $value the case of the column names + * @see http://www.php.net/manual/en/pdo.setattribute.php + */ + public function setColumnCase($value) + { + $this->setAttribute(PDO::ATTR_CASE, $value); + } + + /** + * Returns how the null and empty strings are converted. + * @return mixed how the null and empty strings are converted + * @see http://www.php.net/manual/en/pdo.setattribute.php + */ + public function getNullConversion() + { + return $this->getAttribute(PDO::ATTR_ORACLE_NULLS); + } + + /** + * Sets how the null and empty strings are converted. + * @param mixed $value how the null and empty strings are converted + * @see http://www.php.net/manual/en/pdo.setattribute.php + */ + public function setNullConversion($value) + { + $this->setAttribute(PDO::ATTR_ORACLE_NULLS, $value); + } + + /** + * Returns whether creating or updating a DB record will be automatically committed. + * Some DBMS (such as sqlite) may not support this feature. + * @return boolean whether creating or updating a DB record will be automatically committed. + */ + public function getAutoCommit() + { + return $this->getAttribute(PDO::ATTR_AUTOCOMMIT); + } + + /** + * Sets whether creating or updating a DB record will be automatically committed. + * Some DBMS (such as sqlite) may not support this feature. + * @param boolean $value whether creating or updating a DB record will be automatically committed. + */ + public function setAutoCommit($value) + { + $this->setAttribute(PDO::ATTR_AUTOCOMMIT, $value); + } + + /** + * Returns whether the connection is persistent or not. + * Some DBMS (such as sqlite) may not support this feature. + * @return boolean whether the connection is persistent or not + */ + public function getPersistent() + { + return $this->getAttribute(PDO::ATTR_PERSISTENT); + } + + /** + * Sets whether the connection is persistent or not. + * Some DBMS (such as sqlite) may not support this feature. + * @param boolean $value whether the connection is persistent or not + */ + public function setPersistent($value) + { + return $this->setAttribute(PDO::ATTR_PERSISTENT, $value); + } + + /** + * Returns the name of the DB driver + * @return string name of the DB driver + */ + public function getDriverName() + { + if (($pos = strpos($this->connectionString, ':')) !== false) + return strtolower(substr($this->connectionString, 0, $pos)); + // return $this->getAttribute(PDO::ATTR_DRIVER_NAME); + } + + /** + * Returns the version information of the DB driver. + * @return string the version information of the DB driver + */ + public function getClientVersion() + { + return $this->getAttribute(PDO::ATTR_CLIENT_VERSION); + } + + /** + * Returns the status of the connection. + * Some DBMS (such as sqlite) may not support this feature. + * @return string the status of the connection + */ + public function getConnectionStatus() + { + return $this->getAttribute(PDO::ATTR_CONNECTION_STATUS); + } + + /** + * Returns whether the connection performs data prefetching. + * @return boolean whether the connection performs data prefetching + */ + public function getPrefetch() + { + return $this->getAttribute(PDO::ATTR_PREFETCH); + } + + /** + * Returns the information of DBMS server. + * @return string the information of DBMS server + */ + public function getServerInfo() + { + return $this->getAttribute(PDO::ATTR_SERVER_INFO); + } + + /** + * Returns the version information of DBMS server. + * @return string the version information of DBMS server + */ + public function getServerVersion() + { + return $this->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * Returns the timeout settings for the connection. + * @return integer timeout settings for the connection + */ + public function getTimeout() + { + return $this->getAttribute(PDO::ATTR_TIMEOUT); + } + + /** + * Obtains a specific DB connection attribute information. + * @param integer $name the attribute to be queried + * @return mixed the corresponding attribute information + * @see http://www.php.net/manual/en/function.PDO-getAttribute.php + */ + public function getAttribute($name) + { + $this->setActive(true); + return $this->_pdo->getAttribute($name); + } + + /** + * Sets an attribute on the database connection. + * @param integer $name the attribute to be set + * @param mixed $value the attribute value + * @see http://www.php.net/manual/en/function.PDO-setAttribute.php + */ + public function setAttribute($name, $value) + { + if ($this->_pdo instanceof PDO) + $this->_pdo->setAttribute($name, $value); + else + $this->_attributes[$name] = $value; + } + + /** + * Returns the attributes that are previously explicitly set for the DB connection. + * @return array attributes (name=>value) that are previously explicitly set for the DB connection. + * @see setAttributes + * @since 1.1.7 + */ + public function getAttributes() + { + return $this->_attributes; + } + + /** + * Sets a set of attributes on the database connection. + * @param array $values attributes (name=>value) to be set. + * @see setAttribute + * @since 1.1.7 + */ + public function setAttributes($values) + { + foreach ($values as $name => $value) + $this->_attributes[$name] = $value; + } + + /** + * Returns the statistical results of SQL executions. + * The results returned include the number of SQL statements executed and + * the total time spent. + * In order to use this method, {@link enableProfiling} has to be set true. + * @return array the first element indicates the number of SQL statements executed, + * and the second element the total time spent in SQL execution. + * @since 1.0.6 + */ + public function getStats() + { + $logger = Yii::getLogger(); + $timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.query'); + $count = count($timings); + $time = array_sum($timings); + $timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.execute'); + $count += count($timings); + $time += array_sum($timings); + return array($count, $time); + } +} diff --git a/framework/db/dao/DataReader.php b/framework/db/dao/DataReader.php new file mode 100644 index 0000000..b62a08b --- /dev/null +++ b/framework/db/dao/DataReader.php @@ -0,0 +1,241 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CDbDataReader represents a forward-only stream of rows from a query result set. + * + * To read the current row of data, call {@link read}. The method {@link readAll} + * returns all the rows in a single array. + * + * One can also retrieve the rows of data in CDbDataReader by using foreach: + *
+ * foreach($reader as $row)
+ *     // $row represents a row of data
+ * 
+ * Since CDbDataReader is a forward-only stream, you can only traverse it once. + * + * It is possible to use a specific mode of data fetching by setting + * {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php} + * for more details. + * + * @author Qiang Xue + * @version $Id: CDbDataReader.php 3204 2011-05-05 21:36:32Z alexander.makarow $ + * @package system.db + * @since 1.0 + */ +class CDbDataReader extends CComponent implements Iterator, Countable +{ + private $_statement; + private $_closed = false; + private $_row; + private $_index = -1; + + /** + * Constructor. + * @param CDbCommand $command the command generating the query result + */ + public function __construct(CDbCommand $command) + { + $this->_statement = $command->getPdoStatement(); + $this->_statement->setFetchMode(PDO::FETCH_ASSOC); + } + + /** + * Binds a column to a PHP variable. + * When rows of data are being fetched, the corresponding column value + * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND. + * @param mixed $column Number of the column (1-indexed) or name of the column + * in the result set. If using the column name, be aware that the name + * should match the case of the column, as returned by the driver. + * @param mixed $value Name of the PHP variable to which the column will be bound. + * @param integer $dataType Data type of the parameter + * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php + */ + public function bindColumn($column, &$value, $dataType = null) + { + if ($dataType === null) + $this->_statement->bindColumn($column, $value); + else + $this->_statement->bindColumn($column, $value, $dataType); + } + + /** + * Set the default fetch mode for this statement + * @param mixed $mode fetch mode + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + */ + public function setFetchMode($mode) + { + $params = func_get_args(); + call_user_func_array(array($this->_statement, 'setFetchMode'), $params); + } + + /** + * Advances the reader to the next row in a result set. + * @return array|false the current row, false if no more row available + */ + public function read() + { + return $this->_statement->fetch(); + } + + /** + * Returns a single column from the next row of a result set. + * @param integer $columnIndex zero-based column index + * @return mixed|false the column of the current row, false if no more row available + */ + public function readColumn($columnIndex) + { + return $this->_statement->fetchColumn($columnIndex); + } + + /** + * Returns an object populated with the next row of data. + * @param string $className class name of the object to be created and populated + * @param array $fields Elements of this array are passed to the constructor + * @return mixed|false the populated object, false if no more row of data available + */ + public function readObject($className, $fields) + { + return $this->_statement->fetchObject($className, $fields); + } + + /** + * Reads the whole result set into an array. + * @return array the result set (each array element represents a row of data). + * An empty array will be returned if the result contains no row. + */ + public function readAll() + { + return $this->_statement->fetchAll(); + } + + /** + * Advances the reader to the next result when reading the results of a batch of statements. + * This method is only useful when there are multiple result sets + * returned by the query. Not all DBMS support this feature. + * @return boolean Returns true on success or false on failure. + */ + public function nextResult() + { + if (($result = $this->_statement->nextRowset()) !== false) + $this->_index = -1; + return $result; + } + + /** + * Closes the reader. + * This frees up the resources allocated for executing this SQL statement. + * Read attemps after this method call are unpredictable. + */ + public function close() + { + $this->_statement->closeCursor(); + $this->_closed = true; + } + + /** + * whether the reader is closed or not. + * @return boolean whether the reader is closed or not. + */ + public function getIsClosed() + { + return $this->_closed; + } + + /** + * Returns the number of rows in the result set. + * Note, most DBMS may not give a meaningful count. + * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. + * @return integer number of rows contained in the result. + */ + public function getRowCount() + { + return $this->_statement->rowCount(); + } + + /** + * Returns the number of rows in the result set. + * This method is required by the Countable interface. + * Note, most DBMS may not give a meaningful count. + * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. + * @return integer number of rows contained in the result. + */ + public function count() + { + return $this->getRowCount(); + } + + /** + * Returns the number of columns in the result set. + * Note, even there's no row in the reader, this still gives correct column number. + * @return integer the number of columns in the result set. + */ + public function getColumnCount() + { + return $this->_statement->columnCount(); + } + + /** + * Resets the iterator to the initial state. + * This method is required by the interface Iterator. + * @throws CException if this method is invoked twice + */ + public function rewind() + { + if ($this->_index < 0) + { + $this->_row = $this->_statement->fetch(); + $this->_index = 0; + } + else + throw new CDbException(Yii::t('yii', 'CDbDataReader cannot rewind. It is a forward-only reader.')); + } + + /** + * Returns the index of the current row. + * This method is required by the interface Iterator. + * @return integer the index of the current row. + */ + public function key() + { + return $this->_index; + } + + /** + * Returns the current row. + * This method is required by the interface Iterator. + * @return mixed the current row. + */ + public function current() + { + return $this->_row; + } + + /** + * Moves the internal pointer to the next row. + * This method is required by the interface Iterator. + */ + public function next() + { + $this->_row = $this->_statement->fetch(); + $this->_index++; + } + + /** + * Returns whether there is a row of data at current position. + * This method is required by the interface Iterator. + * @return boolean whether there is a row of data at current position. + */ + public function valid() + { + return $this->_row !== false; + } +} diff --git a/framework/db/dao/Transaction.php b/framework/db/dao/Transaction.php new file mode 100644 index 0000000..0d6a9c6 --- /dev/null +++ b/framework/db/dao/Transaction.php @@ -0,0 +1,108 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CDbTransaction represents a DB transaction. + * + * It is usually created by calling {@link CDbConnection::beginTransaction}. + * + * The following code is a common scenario of using transactions: + *
+ * $transaction=$connection->beginTransaction();
+ * try
+ * {
+ *    $connection->createCommand($sql1)->execute();
+ *    $connection->createCommand($sql2)->execute();
+ *    //.... other SQL executions
+ *    $transaction->commit();
+ * }
+ * catch(Exception $e)
+ * {
+ *    $transaction->rollBack();
+ * }
+ * 
+ * + * @author Qiang Xue + * @version $Id: CDbTransaction.php 3069 2011-03-14 00:28:38Z qiang.xue $ + * @package system.db + * @since 1.0 + */ +class CDbTransaction extends CComponent +{ + private $_connection = null; + private $_active; + + /** + * Constructor. + * @param CDbConnection $connection the connection associated with this transaction + * @see CDbConnection::beginTransaction + */ + public function __construct(CDbConnection $connection) + { + $this->_connection = $connection; + $this->_active = true; + } + + /** + * Commits a transaction. + * @throws CException if the transaction or the DB connection is not active. + */ + public function commit() + { + if ($this->_active && $this->_connection->getActive()) + { + Yii::trace('Committing transaction', 'system.db.CDbTransaction'); + $this->_connection->getPdoInstance()->commit(); + $this->_active = false; + } + else + throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.')); + } + + /** + * Rolls back a transaction. + * @throws CException if the transaction or the DB connection is not active. + */ + public function rollback() + { + if ($this->_active && $this->_connection->getActive()) + { + Yii::trace('Rolling back transaction', 'system.db.CDbTransaction'); + $this->_connection->getPdoInstance()->rollBack(); + $this->_active = false; + } + else + throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.')); + } + + /** + * @return CDbConnection the DB connection for this transaction + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return boolean whether this transaction is active + */ + public function getActive() + { + return $this->_active; + } + + /** + * @param boolean $value whether this transaction is active + */ + protected function setActive($value) + { + $this->_active = $value; + } +} diff --git a/framework/db/pdo/Command.php b/framework/db/pdo/Command.php deleted file mode 100644 index 92ff6a9..0000000 --- a/framework/db/pdo/Command.php +++ /dev/null @@ -1,1499 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CDbCommand represents an SQL statement to execute against a database. - * - * It is usually created by calling {@link CDbConnection::createCommand}. - * The SQL statement to be executed may be set via {@link setText Text}. - * - * To execute a non-query SQL (such as insert, delete, update), call - * {@link execute}. To execute an SQL statement that returns result data set - * (such as SELECT), use {@link query} or its convenient versions {@link queryRow}, - * {@link queryColumn}, or {@link queryScalar}. - * - * If an SQL statement returns results (such as a SELECT SQL), the results - * can be accessed via the returned {@link CDbDataReader}. - * - * CDbCommand supports SQL statment preparation and parameter binding. - * Call {@link bindParam} to bind a PHP variable to a parameter in SQL. - * Call {@link bindValue} to bind a value to an SQL parameter. - * When binding a parameter, the SQL statement is automatically prepared. - * You may also call {@link prepare} to explicitly prepare an SQL statement. - * - * Starting from version 1.1.6, CDbCommand can also be used as a query builder - * that builds a SQL statement from code fragments. For example, - *
- * $user = Yii::app()->db->createCommand()
- *     ->select('username, password')
- *     ->from('tbl_user')
- *     ->where('id=:id', array(':id'=>1))
- *     ->queryRow();
- * 
- * - * @author Qiang Xue - * @version $Id: CDbCommand.php 3240 2011-05-25 19:22:47Z qiang.xue $ - * @package system.db - * @since 1.0 - */ -class CDbCommand extends CComponent -{ - /** - * @var array the parameters (name=>value) to be bound to the current query. - * @since 1.1.6 - */ - public $params = array(); - - private $_connection; - private $_text; - private $_statement; - private $_paramLog = array(); - private $_query; - private $_fetchMode = array(PDO::FETCH_ASSOC); - - /** - * Constructor. - * @param CDbConnection $connection the database connection - * @param mixed $query the DB query to be executed. This can be either - * a string representing a SQL statement, or an array whose name-value pairs - * will be used to set the corresponding properties of the created command object. - * - * For example, you can pass in either 'SELECT * FROM tbl_user' - * or array('select'=>'*', 'from'=>'tbl_user'). They are equivalent - * in terms of the final query result. - * - * When passing the query as an array, the following properties are commonly set: - * {@link select}, {@link distinct}, {@link from}, {@link where}, {@link join}, - * {@link group}, {@link having}, {@link order}, {@link limit}, {@link offset} and - * {@link union}. Please refer to the setter of each of these properties for details - * about valid property values. This feature has been available since version 1.1.6. - * - * Since 1.1.7 it is possible to use a specific mode of data fetching by setting - * {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php} - * for more details. - */ - public function __construct(CDbConnection $connection, $query = null) - { - $this->_connection = $connection; - if (is_array($query)) - { - foreach ($query as $name => $value) - $this->$name = $value; - } - else - $this->setText($query); - } - - /** - * Set the statement to null when serializing. - * @return array - */ - public function __sleep() - { - $this->_statement = null; - return array_keys(get_object_vars($this)); - } - - /** - * Set the default fetch mode for this statement - * @param mixed $mode fetch mode - * @return CDbCommand - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - * @since 1.1.7 - */ - public function setFetchMode($mode) - { - $params = func_get_args(); - $this->_fetchMode = $params; - return $this; - } - - /** - * Cleans up the command and prepares for building a new query. - * This method is mainly used when a command object is being reused - * multiple times for building different queries. - * Calling this method will clean up all internal states of the command object. - * @return CDbCommand this command instance - * @since 1.1.6 - */ - public function reset() - { - $this->_text = null; - $this->_query = null; - $this->_statement = null; - $this->_paramLog = array(); - $this->params = array(); - return $this; - } - - /** - * @return string the SQL statement to be executed - */ - public function getText() - { - if ($this->_text == '' && !empty($this->_query)) - $this->setText($this->buildQuery($this->_query)); - return $this->_text; - } - - /** - * Specifies the SQL statement to be executed. - * Any previous execution will be terminated or cancel. - * @param string $value the SQL statement to be executed - * @return CDbCommand this command instance - */ - public function setText($value) - { - if ($this->_connection->tablePrefix !== null && $value != '') - $this->_text = preg_replace('/{{(.*?)}}/', $this->_connection->tablePrefix . '\1', $value); - else - $this->_text = $value; - $this->cancel(); - return $this; - } - - /** - * @return CDbConnection the connection associated with this command - */ - public function getConnection() - { - return $this->_connection; - } - - /** - * @return PDOStatement the underlying PDOStatement for this command - * It could be null if the statement is not prepared yet. - */ - public function getPdoStatement() - { - return $this->_statement; - } - - /** - * Prepares the SQL statement to be executed. - * For complex SQL statement that is to be executed multiple times, - * this may improve performance. - * For SQL statement with binding parameters, this method is invoked - * automatically. - */ - public function prepare() - { - if ($this->_statement == null) - { - try - { - $this->_statement = $this->getConnection()->getPdoInstance()->prepare($this->getText()); - $this->_paramLog = array(); - } - catch(Exception $e) - { - Yii::log('Error in preparing SQL: ' . $this->getText(), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); - $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; - throw new CDbException(Yii::t('yii', 'CDbCommand failed to prepare the SQL statement: {error}', - array('{error}' => $e->getMessage())), (int)$e->getCode(), $errorInfo); - } - } - } - - /** - * Cancels the execution of the SQL statement. - */ - public function cancel() - { - $this->_statement = null; - } - - /** - * Binds a parameter to the SQL statement to be executed. - * @param mixed $name Parameter identifier. For a prepared statement - * using named placeholders, this will be a parameter name of - * the form :name. For a prepared statement using question mark - * placeholders, this will be the 1-indexed position of the parameter. - * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter - * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @param integer $length length of the data type - * @param mixed $driverOptions the driver-specific options (this is available since version 1.1.6) - * @return CDbCommand the current command being executed (this is available since version 1.0.8) - * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php - */ - public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) - { - $this->prepare(); - if ($dataType === null) - $this->_statement->bindParam($name, $value, $this->_connection->getPdoType(gettype($value))); - elseif ($length === null) - $this->_statement->bindParam($name, $value, $dataType); - elseif ($driverOptions === null) - $this->_statement->bindParam($name, $value, $dataType, $length); - else - $this->_statement->bindParam($name, $value, $dataType, $length, $driverOptions); - $this->_paramLog[$name] =& $value; - return $this; - } - - /** - * Binds a value to a parameter. - * @param mixed $name Parameter identifier. For a prepared statement - * using named placeholders, this will be a parameter name of - * the form :name. For a prepared statement using question mark - * placeholders, this will be the 1-indexed position of the parameter. - * @param mixed $value The value to bind to the parameter - * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. - * @return CDbCommand the current command being executed (this is available since version 1.0.8) - * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php - */ - public function bindValue($name, $value, $dataType = null) - { - $this->prepare(); - if ($dataType === null) - $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); - else - $this->_statement->bindValue($name, $value, $dataType); - $this->_paramLog[$name] = $value; - return $this; - } - - /** - * Binds a list of values to the corresponding parameters. - * This is similar to {@link bindValue} except that it binds multiple values. - * Note that the SQL data type of each value is determined by its PHP type. - * @param array $values the values to be bound. This must be given in terms of an associative - * array with array keys being the parameter names, and array values the corresponding parameter values. - * For example, array(':name'=>'John', ':age'=>25). - * @return CDbCommand the current command being executed - * @since 1.1.5 - */ - public function bindValues($values) - { - $this->prepare(); - foreach ($values as $name => $value) - { - $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); - $this->_paramLog[$name] = $value; - } - return $this; - } - - /** - * Executes the SQL statement. - * This method is meant only for executing non-query SQL statement. - * No result set will be returned. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return integer number of rows affected by the execution. - * @throws CException execution failed - */ - public function execute($params = array()) - { - if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) - { - $p = array(); - foreach ($pars as $name => $value) - $p[$name] = $name . '=' . var_export($value, true); - $par = '. Bound with ' . implode(', ', $p); - } - else - $par = ''; - Yii::trace('Executing SQL: ' . $this->getText() . $par, 'system.db.CDbCommand'); - try - { - if ($this->_connection->enableProfiling) - Yii::beginProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); - - $this->prepare(); - if ($params === array()) - $this->_statement->execute(); - else - $this->_statement->execute($params); - $n = $this->_statement->rowCount(); - - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); - - return $n; - } - catch(Exception $e) - { - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute'); - $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; - $message = $e->getMessage(); - Yii::log(Yii::t('yii', 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.', - array('{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); - if (YII_DEBUG) - $message .= '. The SQL statement executed was: ' . $this->getText() . $par; - throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}', - array('{error}' => $message)), (int)$e->getCode(), $errorInfo); - } - } - - /** - * Executes the SQL statement and returns query result. - * This method is for executing an SQL query that returns result set. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return CDbDataReader the reader object for fetching the query result - * @throws CException execution failed - */ - public function query($params = array()) - { - return $this->queryInternal('', 0, $params); - } - - /** - * Executes the SQL statement and returns all rows. - * @param boolean $fetchAssociative whether each row should be returned as an associated array with - * column names as the keys or the array keys are column indexes (0-based). - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return array all rows of the query result. Each array element is an array representing a row. - * An empty array is returned if the query results in nothing. - * @throws CException execution failed - */ - public function queryAll($fetchAssociative = true, $params = array()) - { - return $this->queryInternal('fetchAll', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); - } - - /** - * Executes the SQL statement and returns the first row of the result. - * This is a convenient method of {@link query} when only the first row of data is needed. - * @param boolean $fetchAssociative whether the row should be returned as an associated array with - * column names as the keys or the array keys are column indexes (0-based). - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return mixed the first row (in terms of an array) of the query result, false if no result. - * @throws CException execution failed - */ - public function queryRow($fetchAssociative = true, $params = array()) - { - return $this->queryInternal('fetch', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); - } - - /** - * Executes the SQL statement and returns the value of the first column in the first row of data. - * This is a convenient method of {@link query} when only a single scalar - * value is needed (e.g. obtaining the count of the records). - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return mixed the value of the first column in the first row of the query result. False is returned if there is no value. - * @throws CException execution failed - */ - public function queryScalar($params = array()) - { - $result = $this->queryInternal('fetchColumn', 0, $params); - if (is_resource($result) && get_resource_type($result) === 'stream') - return stream_get_contents($result); - else - return $result; - } - - /** - * Executes the SQL statement and returns the first column of the result. - * This is a convenient method of {@link query} when only the first column of data is needed. - * Note, the column returned will contain the first element in each row of result. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that if you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return array the first column of the query result. Empty array if no result. - * @throws CException execution failed - */ - public function queryColumn($params = array()) - { - return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN, $params); - } - - /** - * @param string $method method of PDOStatement to be called - * @param mixed $mode parameters to be passed to the method - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing - * them in this way can improve the performance. Note that you pass parameters in this way, - * you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa. - * binding methods and the input parameters this way can improve the performance. - * This parameter has been available since version 1.0.10. - * @return mixed the method execution result - */ - private function queryInternal($method, $mode, $params = array()) - { - $params = array_merge($this->params, $params); - - if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) - { - $p = array(); - foreach ($pars as $name => $value) - $p[$name] = $name . '=' . var_export($value, true); - $par = '. Bound with ' . implode(', ', $p); - } - else - $par = ''; - - Yii::trace('Querying SQL: ' . $this->getText() . $par, 'system.db.CDbCommand'); - - if ($this->_connection->queryCachingCount > 0 && $method !== '' - && $this->_connection->queryCachingDuration > 0 - && $this->_connection->queryCacheID !== false - && ($cache = Yii::app()->getComponent($this->_connection->queryCacheID)) !== null) - { - $this->_connection->queryCachingCount--; - $cacheKey = 'yii:dbquery' . $this->_connection->connectionString . ':' . $this->_connection->username; - $cacheKey .= ':' . $this->getText() . ':' . serialize(array_merge($this->_paramLog, $params)); - if (($result = $cache->get($cacheKey)) !== false) - { - Yii::trace('Query result found in cache', 'system.db.CDbCommand'); - return $result; - } - } - - try - { - if ($this->_connection->enableProfiling) - Yii::beginProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); - - $this->prepare(); - if ($params === array()) - $this->_statement->execute(); - else - $this->_statement->execute($params); - - if ($method === '') - $result = new CDbDataReader($this); - else - { - $mode = (array)$mode; - $result = call_user_func_array(array($this->_statement, $method), $mode); - $this->_statement->closeCursor(); - } - - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); - - if (isset($cache, $cacheKey)) - $cache->set($cacheKey, $result, $this->_connection->queryCachingDuration, $this->_connection->queryCachingDependency); - - return $result; - } - catch(Exception $e) - { - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query'); - $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; - $message = $e->getMessage(); - Yii::log(Yii::t('yii', 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.', - array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand'); - if (YII_DEBUG) - $message .= '. The SQL statement executed was: ' . $this->getText() . $par; - throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}', - array('{error}' => $message)), (int)$e->getCode(), $errorInfo); - } - } - - /** - * Builds a SQL SELECT statement from the given query specification. - * @param array $query the query specification in name-value pairs. The following - * query options are supported: {@link select}, {@link distinct}, {@link from}, - * {@link where}, {@link join}, {@link group}, {@link having}, {@link order}, - * {@link limit}, {@link offset} and {@link union}. - * @return string the SQL statement - * @since 1.1.6 - */ - public function buildQuery($query) - { - $sql = isset($query['distinct']) && $query['distinct'] ? 'SELECT DISTINCT' : 'SELECT'; - $sql .= ' ' . (isset($query['select']) ? $query['select'] : '*'); - - if (isset($query['from'])) - $sql .= "\nFROM " . $query['from']; - else - throw new CDbException(Yii::t('yii', 'The DB query must contain the "from" portion.')); - - if (isset($query['join'])) - $sql .= "\n" . (is_array($query['join']) ? implode("\n", $query['join']) : $query['join']); - - if (isset($query['where'])) - $sql .= "\nWHERE " . $query['where']; - - if (isset($query['group'])) - $sql .= "\nGROUP BY " . $query['group']; - - if (isset($query['having'])) - $sql .= "\nHAVING " . $query['having']; - - if (isset($query['order'])) - $sql .= "\nORDER BY " . $query['order']; - - $limit = isset($query['limit']) ? (int)$query['limit'] : -1; - $offset = isset($query['offset']) ? (int)$query['offset'] : -1; - if ($limit >= 0 || $offset > 0) - $sql = $this->_connection->getCommandBuilder()->applyLimit($sql, $limit, $offset); - - if (isset($query['union'])) - $sql .= "\nUNION (\n" . (is_array($query['union']) ? implode("\n) UNION (\n", $query['union']) : $query['union']) . ')'; - - return $sql; - } - - /** - * Sets the SELECT part of the query. - * @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns. - * 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. This parameter is supported since version 1.1.8. - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function select($columns = '*', $option = '') - { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['select'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $column, $matches)) - $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' AS ' . $this->_connection->quoteColumnName($matches[2]); - else - $columns[$i] = $this->_connection->quoteColumnName($column); - } - } - $this->_query['select'] = implode(', ', $columns); - } - if ($option != '') - $this->_query['select'] = $option . ' ' . $this->_query['select']; - return $this; - } - - /** - * Returns the SELECT part in the query. - * @return string the SELECT part (without 'SELECT') in the query. - * @since 1.1.6 - */ - public function getSelect() - { - return isset($this->_query['select']) ? $this->_query['select'] : ''; - } - - /** - * Sets the SELECT part in the query. - * @param mixed $value the data to be selected. Please refer to {@link select()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setSelect($value) - { - $this->select($value); - } - - /** - * Sets the SELECT part of the query with the DISTINCT flag turned on. - * This is the same as {@link select} except that the DISTINCT flag is turned on. - * @param mixed $columns the columns to be selected. See {@link select} for more details. - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function selectDistinct($columns = '*') - { - $this->_query['distinct'] = true; - return $this->select($columns); - } - - /** - * Returns a value indicating whether SELECT DISTINCT should be used. - * @return boolean a value indicating whether SELECT DISTINCT should be used. - * @since 1.1.6 - */ - public function getDistinct() - { - return isset($this->_query['distinct']) ? $this->_query['distinct'] : false; - } - - /** - * Sets a value indicating whether SELECT DISTINCT should be used. - * @param boolean $value a value indicating whether SELECT DISTINCT should be used. - * @since 1.1.6 - */ - public function setDistinct($value) - { - $this->_query['distinct'] = $value; - } - - /** - * Sets the FROM part of the query. - * @param mixed $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 CDbCommand the command object itself - * @since 1.1.6 - */ - public function from($tables) - { - if (is_string($tables) && strpos($tables, '(') !== false) - $this->_query['from'] = $tables; - else - { - if (!is_array($tables)) - $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY); - foreach ($tables as $i => $table) - { - if (strpos($table, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias - $tables[$i] = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); - else - $tables[$i] = $this->_connection->quoteTableName($table); - } - } - $this->_query['from'] = implode(', ', $tables); - } - return $this; - } - - /** - * Returns the FROM part in the query. - * @return string the FROM part (without 'FROM' ) in the query. - * @since 1.1.6 - */ - public function getFrom() - { - return isset($this->_query['from']) ? $this->_query['from'] : ''; - } - - /** - * Sets the FROM part in the query. - * @param mixed $value the tables to be selected from. Please refer to {@link from()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setFrom($value) - { - $this->from($value); - } - - /** - * Sets the WHERE part of the query. - * - * The method requires a $conditions parameter, and optionally a $params parameter - * specifying the values to be bound to the query. - * - * The $conditions parameter should be either a string (e.g. 'id=1') or an array. - * If the latter, it must be of the format array(operator, operand1, operand2, ...), - * where the operator can be one of the followings, and the possible operands depend on the corresponding - * operator: - *
    - *
  • 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 same 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 as the and operator except that the operands are concatenated using OR.
  • - *
  • 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 as 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.
  • - *
  • not like: similar as the like operator except that LIKE is replaced with NOT LIKE in the generated condition.
  • - *
  • or like: similar as the like operator except that OR is used to concatenated the LIKE predicates.
  • - *
  • or not like: similar as the not like operator except that OR is used to concatenated the NOT LIKE predicates.
  • - *
- * @param mixed $conditions the conditions that should be put in the WHERE part. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function where($conditions, $params = array()) - { - $this->_query['where'] = $this->processConditions($conditions); - foreach ($params as $name => $value) - $this->params[$name] = $value; - return $this; - } - - /** - * Returns the WHERE part in the query. - * @return string the WHERE part (without 'WHERE' ) in the query. - * @since 1.1.6 - */ - public function getWhere() - { - return isset($this->_query['where']) ? $this->_query['where'] : ''; - } - - /** - * Sets the WHERE part in the query. - * @param mixed $value the where part. Please refer to {@link where()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setWhere($value) - { - $this->where($value); - } - - /** - * 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 mixed $conditions the join condition that should appear in the ON part. - * Please refer to {@link where} on how to specify conditions. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function join($table, $conditions, $params = array()) - { - return $this->joinInternal('join', $table, $conditions, $params); - } - - /** - * Returns the join part in the query. - * @return mixed the join part in the query. This can be an array representing - * multiple join fragments, or a string representing a single jojin fragment. - * Each join fragment will contain the proper join operator (e.g. LEFT JOIN). - * @since 1.1.6 - */ - public function getJoin() - { - return isset($this->_query['join']) ? $this->_query['join'] : ''; - } - - /** - * Sets the join part in the query. - * @param mixed $value the join part in the query. This can be either a string or - * an array representing multiple join parts in the query. Each part must contain - * the proper join operator (e.g. 'LEFT JOIN tbl_profile ON tbl_user.id=tbl_profile.id') - * @since 1.1.6 - */ - public function setJoin($value) - { - $this->_query['join'] = $value; - } - - /** - * 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 mixed $conditions the join condition that should appear in the ON part. - * Please refer to {@link where} on how to specify conditions. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function leftJoin($table, $conditions, $params = array()) - { - return $this->joinInternal('left join', $table, $conditions, $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 mixed $conditions the join condition that should appear in the ON part. - * Please refer to {@link where} on how to specify conditions. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function rightJoin($table, $conditions, $params = array()) - { - return $this->joinInternal('right join', $table, $conditions, $params); - } - - /** - * Appends a CROSS JOIN part to the query. - * Note that not all DBMS support CROSS 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). - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function crossJoin($table) - { - return $this->joinInternal('cross join', $table); - } - - /** - * Appends a NATURAL JOIN part to the query. - * Note that not all DBMS support NATURAL 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). - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function naturalJoin($table) - { - return $this->joinInternal('natural join', $table); - } - - /** - * Sets the GROUP BY part of the query. - * @param mixed $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 CDbCommand the command object itself - * @since 1.1.6 - */ - public function group($columns) - { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['group'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - $columns[$i] = $this->_connection->quoteColumnName($column); - } - $this->_query['group'] = implode(', ', $columns); - } - return $this; - } - - /** - * Returns the GROUP BY part in the query. - * @return string the GROUP BY part (without 'GROUP BY' ) in the query. - * @since 1.1.6 - */ - public function getGroup() - { - return isset($this->_query['group']) ? $this->_query['group'] : ''; - } - - /** - * Sets the GROUP BY part in the query. - * @param mixed $value the GROUP BY part. Please refer to {@link group()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setGroup($value) - { - $this->group($value); - } - - /** - * Sets the HAVING part of the query. - * @param mixed $conditions the conditions to be put after HAVING. - * Please refer to {@link where} on how to specify conditions. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function having($conditions, $params = array()) - { - $this->_query['having'] = $this->processConditions($conditions); - foreach ($params as $name => $value) - $this->params[$name] = $value; - return $this; - } - - /** - * Returns the HAVING part in the query. - * @return string the HAVING part (without 'HAVING' ) in the query. - * @since 1.1.6 - */ - public function getHaving() - { - return isset($this->_query['having']) ? $this->_query['having'] : ''; - } - - /** - * Sets the HAVING part in the query. - * @param mixed $value the HAVING part. Please refer to {@link having()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setHaving($value) - { - $this->having($value); - } - - /** - * Sets the ORDER BY part of the query. - * @param mixed $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 CDbCommand the command object itself - * @since 1.1.6 - */ - public function order($columns) - { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['order'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - { - if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) - $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' ' . strtoupper($matches[2]); - else - $columns[$i] = $this->_connection->quoteColumnName($column); - } - } - $this->_query['order'] = implode(', ', $columns); - } - return $this; - } - - /** - * Returns the ORDER BY part in the query. - * @return string the ORDER BY part (without 'ORDER BY' ) in the query. - * @since 1.1.6 - */ - public function getOrder() - { - return isset($this->_query['order']) ? $this->_query['order'] : ''; - } - - /** - * Sets the ORDER BY part in the query. - * @param mixed $value the ORDER BY part. Please refer to {@link order()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setOrder($value) - { - $this->order($value); - } - - /** - * Sets the LIMIT part of the query. - * @param integer $limit the limit - * @param integer $offset the offset - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function limit($limit, $offset = null) - { - $this->_query['limit'] = (int)$limit; - if ($offset !== null) - $this->offset($offset); - return $this; - } - - /** - * Returns the LIMIT part in the query. - * @return string the LIMIT part (without 'LIMIT' ) in the query. - * @since 1.1.6 - */ - public function getLimit() - { - return isset($this->_query['limit']) ? $this->_query['limit'] : -1; - } - - /** - * Sets the LIMIT part in the query. - * @param integer $value the LIMIT part. Please refer to {@link limit()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setLimit($value) - { - $this->limit($value); - } - - /** - * Sets the OFFSET part of the query. - * @param integer $offset the offset - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function offset($offset) - { - $this->_query['offset'] = (int)$offset; - return $this; - } - - /** - * Returns the OFFSET part in the query. - * @return string the OFFSET part (without 'OFFSET' ) in the query. - * @since 1.1.6 - */ - public function getOffset() - { - return isset($this->_query['offset']) ? $this->_query['offset'] : -1; - } - - /** - * Sets the OFFSET part in the query. - * @param integer $value the OFFSET part. Please refer to {@link offset()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setOffset($value) - { - $this->offset($value); - } - - /** - * Appends a SQL statement using UNION operator. - * @param string $sql the SQL statement to be appended using UNION - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - public function union($sql) - { - if (isset($this->_query['union']) && is_string($this->_query['union'])) - $this->_query['union'] = array($this->_query['union']); - - $this->_query['union'][] = $sql; - - return $this; - } - - /** - * Returns the UNION part in the query. - * @return mixed the UNION part (without 'UNION' ) in the query. - * This can be either a string or an array representing multiple union parts. - * @since 1.1.6 - */ - public function getUnion() - { - return isset($this->_query['union']) ? $this->_query['union'] : ''; - } - - /** - * Sets the UNION part in the query. - * @param mixed $value the UNION part. This can be either a string or an array - * representing multiple SQL statements to be unioned together. - * @since 1.1.6 - */ - public function setUnion($value) - { - $this->_query['union'] = $value; - } - - /** - * Creates and executes an INSERT SQL statement. - * The method will properly escape the column names, and bind the values to be inserted. - * @param string $table the table that new rows will be inserted into. - * @param array $columns the column data (name=>value) to be inserted into the table. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function insert($table, $columns) - { - $params = array(); - $names = array(); - $placeholders = array(); - foreach ($columns as $name => $value) - { - $names[] = $this->_connection->quoteColumnName($name); - if ($value instanceof CDbExpression) - { - $placeholders[] = $value->expression; - foreach ($value->params as $n => $v) - $params[$n] = $v; - } - else - { - $placeholders[] = ':' . $name; - $params[':' . $name] = $value; - } - } - $sql = 'INSERT INTO ' . $this->_connection->quoteTableName($table) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; - return $this->setText($sql)->execute($params); - } - - /** - * Creates and executes an UPDATE SQL statement. - * The method will properly escape the column names and bind the values to be updated. - * @param string $table the table to be updated. - * @param array $columns the column data (name=>value) to be updated. - * @param mixed $conditions the conditions that will be put in the WHERE part. Please - * refer to {@link where} on how to specify conditions. - * @param array $params the parameters to be bound to the query. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function update($table, $columns, $conditions = '', $params = array()) - { - $lines = array(); - foreach ($columns as $name => $value) - { - if ($value instanceof CDbExpression) - { - $lines[] = $this->_connection->quoteColumnName($name) . '=' . $value->expression; - foreach ($value->params as $n => $v) - $params[$n] = $v; - } - else - { - $lines[] = $this->_connection->quoteColumnName($name) . '=:' . $name; - $params[':' . $name] = $value; - } - } - $sql = 'UPDATE ' . $this->_connection->quoteTableName($table) . ' SET ' . implode(', ', $lines); - if (($where = $this->processConditions($conditions)) != '') - $sql .= ' WHERE ' . $where; - return $this->setText($sql)->execute($params); - } - - /** - * Creates and executes a DELETE SQL statement. - * @param string $table the table where the data will be deleted from. - * @param mixed $conditions the conditions that will be put in the WHERE part. Please - * refer to {@link where} on how to specify conditions. - * @param array $params the parameters to be bound to the query. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function delete($table, $conditions = '', $params = array()) - { - $sql = 'DELETE FROM ' . $this->_connection->quoteTableName($table); - if (($where = $this->processConditions($conditions)) != '') - $sql .= ' WHERE ' . $where; - return $this->setText($sql)->execute($params); - } - - /** - * Builds and executes a SQL statement for creating a new DB table. - * - * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), - * where name stands for a column name which will be properly quoted by the method, and definition - * stands for the column type which can contain an abstract DB type. - * The {@link getColumnType} method will be invoked to convert any abstract type into a physical one. - * - * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly - * inserted into the generated SQL. - * - * @param string $table the name of the table to be created. The name will be properly quoted by the method. - * @param array $columns the columns (name=>definition) in the new table. - * @param string $options additional SQL fragment that will be appended to the generated SQL. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function createTable($table, $columns, $options = null) - { - return $this->setText($this->getConnection()->getSchema()->createTable($table, $columns, $options))->execute(); - } - - /** - * Builds and executes a SQL statement for renaming a DB table. - * @param string $table the table to be renamed. The name will be properly quoted by the method. - * @param string $newName the new table name. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function renameTable($table, $newName) - { - return $this->setText($this->getConnection()->getSchema()->renameTable($table, $newName))->execute(); - } - - /** - * Builds and executes a SQL statement for dropping a DB table. - * @param string $table the table to be dropped. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function dropTable($table) - { - return $this->setText($this->getConnection()->getSchema()->dropTable($table))->execute(); - } - - /** - * Builds and executes a SQL statement for truncating a DB table. - * @param string $table the table to be truncated. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function truncateTable($table) - { - $schema = $this->getConnection()->getSchema(); - $n = $this->setText($schema->truncateTable($table))->execute(); - if (strncasecmp($this->getConnection()->getDriverName(), 'sqlite', 6) === 0) - $schema->resetSequence($schema->getTable($table)); - return $n; - } - - /** - * Builds and executes a SQL statement for adding a new DB column. - * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. - * @param string $column the name of the new column. The name will be properly quoted by the method. - * @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) - * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. - * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function addColumn($table, $column, $type) - { - return $this->setText($this->getConnection()->getSchema()->addColumn($table, $column, $type))->execute(); - } - - /** - * Builds and executes a SQL statement for dropping a DB column. - * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. - * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function dropColumn($table, $column) - { - return $this->setText($this->getConnection()->getSchema()->dropColumn($table, $column))->execute(); - } - - /** - * Builds and executes a SQL statement for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $name the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function renameColumn($table, $name, $newName) - { - return $this->setText($this->getConnection()->getSchema()->renameColumn($table, $name, $newName))->execute(); - } - - /** - * Builds and executes a SQL statement for changing the definition of a column. - * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. - * @param string $column the name of the column to be changed. The name will be properly quoted by the method. - * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) - * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. - * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function alterColumn($table, $column, $type) - { - return $this->setText($this->getConnection()->getSchema()->alterColumn($table, $column, $type))->execute(); - } - - /** - * Builds a SQL statement for adding a foreign key constraint to an existing table. - * The method will properly quote the table and column names. - * @param string $name the name of the foreign key constraint. - * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. - * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. - * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - return $this->setText($this->getConnection()->getSchema()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update))->execute(); - } - - /** - * Builds a SQL statement for dropping a foreign key constraint. - * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function dropForeignKey($name, $table) - { - return $this->setText($this->getConnection()->getSchema()->dropForeignKey($name, $table))->execute(); - } - - /** - * Builds and executes a SQL statement for creating a new index. - * @param string $name the name of the index. The name will be properly quoted by the method. - * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. The column names will be properly quoted by the method. - * @param boolean $unique whether to add UNIQUE constraint on the created index. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function createIndex($name, $table, $column, $unique = false) - { - return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute(); - } - - /** - * Builds and executes a SQL statement for dropping an index. - * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. - * @return integer number of rows affected by the execution. - * @since 1.1.6 - */ - public function dropIndex($name, $table) - { - return $this->setText($this->getConnection()->getSchema()->dropIndex($name, $table))->execute(); - } - - /** - * Generates the condition string that will be put in the WHERE part - * @param mixed $conditions the conditions that will be put in the WHERE part. - * @return string the condition string to put in the WHERE part - */ - private function processConditions($conditions) - { - if (!is_array($conditions)) - return $conditions; - elseif ($conditions === array()) - return ''; - $n = count($conditions); - $operator = strtoupper($conditions[0]); - if ($operator === 'OR' || $operator === 'AND') - { - $parts = array(); - for ($i = 1;$i < $n;++$i) - { - $condition = $this->processConditions($conditions[$i]); - if ($condition !== '') - $parts[] = '(' . $condition . ')'; - } - return $parts === array() ? '' : implode(' ' . $operator . ' ', $parts); - } - - if (!isset($conditions[1], $conditions[2])) - return ''; - - $column = $conditions[1]; - if (strpos($column, '(') === false) - $column = $this->_connection->quoteColumnName($column); - - $values = $conditions[2]; - if (!is_array($values)) - $values = array($values); - - if ($operator === 'IN' || $operator === 'NOT IN') - { - if ($values === array()) - return $operator === 'IN' ? '0=1' : ''; - foreach ($values as $i => $value) - { - if (is_string($value)) - $values[$i] = $this->_connection->quoteValue($value); - else - $values[$i] = (string)$value; - } - return $column . ' ' . $operator . ' (' . implode(', ', $values) . ')'; - } - - if ($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE') - { - if ($values === array()) - return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : ''; - - if ($operator === 'LIKE' || $operator === 'NOT LIKE') - $andor = ' AND '; - else - { - $andor = ' OR '; - $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; - } - $expressions = array(); - foreach ($values as $value) - $expressions[] = $column . ' ' . $operator . ' ' . $this->_connection->quoteValue($value); - return implode($andor, $expressions); - } - - throw new CDbException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}' => $operator))); - } - - /** - * Appends an JOIN part to the query. - * @param string $type the join type ('join', 'left join', 'right join', 'cross join', 'natural 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 mixed $conditions the join condition that should appear in the ON part. - * Please refer to {@link where} on how to specify conditions. - * @param array $params the parameters (name=>value) to be bound to the query - * @return CDbCommand the command object itself - * @since 1.1.6 - */ - private function joinInternal($type, $table, $conditions = '', $params = array()) - { - if (strpos($table, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias - $table = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); - else - $table = $this->_connection->quoteTableName($table); - } - - $conditions = $this->processConditions($conditions); - if ($conditions != '') - $conditions = ' ON ' . $conditions; - - if (isset($this->_query['join']) && is_string($this->_query['join'])) - $this->_query['join'] = array($this->_query['join']); - - $this->_query['join'][] = strtoupper($type) . ' ' . $table . $conditions; - - foreach ($params as $name => $value) - $this->params[$name] = $value; - return $this; - } -} diff --git a/framework/db/pdo/Connection.php b/framework/db/pdo/Connection.php deleted file mode 100644 index 4c99c57..0000000 --- a/framework/db/pdo/Connection.php +++ /dev/null @@ -1,792 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2012 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * Connection represents a connection to a database. - * - * Connection works together with {@link CDbCommand}, {@link CDbDataReader} - * and {@link CDbTransaction} to provide data access to various DBMS - * in a common set of APIs. They are a thin wrapper of the {@link http://www.php.net/manual/en/ref.pdo.php PDO} - * PHP extension. - * - * To establish a connection, set {@link setActive active} to true after - * specifying {@link connectionString}, {@link username} and {@link password}. - * - * The following example shows how to create a Connection instance and establish - * the actual connection: - *
- * $connection=new Connection($dsn,$username,$password);
- * $connection->active=true;
- * 
- * - * After the DB connection is established, one can execute an SQL statement like the following: - *
- * $command=$connection->createCommand($sqlStatement);
- * $command->execute();   // a non-query SQL statement execution
- * // or execute an SQL query and fetch the result set
- * $reader=$command->query();
- *
- * // each $row is an array representing a row of data
- * foreach($reader as $row) ...
- * 
- * - * One can do prepared SQL execution and bind parameters to the prepared SQL: - *
- * $command=$connection->createCommand($sqlStatement);
- * $command->bindParam($name1,$value1);
- * $command->bindParam($name2,$value2);
- * $command->execute();
- * 
- * - * To use transaction, do like the following: - *
- * $transaction=$connection->beginTransaction();
- * try
- * {
- *    $connection->createCommand($sql1)->execute();
- *    $connection->createCommand($sql2)->execute();
- *    //.... other SQL executions
- *    $transaction->commit();
- * }
- * catch(Exception $e)
- * {
- *    $transaction->rollBack();
- * }
- * 
- * - * Connection also provides a set of methods to support setting and querying - * of certain DBMS attributes, such as {@link getNullConversion nullConversion}. - * - * Since Connection implements the interface IApplicationComponent, it can - * be used as an application component and be configured in application configuration, - * like the following, - *
- * array(
- *     'components'=>array(
- *         'db'=>array(
- *             'class'=>'Connection',
- *             'connectionString'=>'sqlite:path/to/dbfile',
- *         ),
- *     ),
- * )
- * 
- * - * @author Qiang Xue - * @since 2.0 - */ -class Connection extends \yii\base\ApplicationComponent -{ - /** - * @var string The Data Source Name, or DSN, contains the information required to connect to the database. - * @see http://www.php.net/manual/en/function.PDO-construct.php - * - * Note that if your database is using GBK or BIG5 charset, we highly recommend you - * to upgrade to PHP 5.3.6+ and specify charset via DSN like the following to prevent - * from hacking: `mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;`. - */ - public $dsn; - /** - * @var string the username for establishing DB connection. Defaults to empty string. - */ - public $username = ''; - /** - * @var string the password for establishing DB connection. Defaults to empty string. - */ - public $password = ''; - /** - * @var integer number of seconds that table metadata can remain valid in cache. - * Use 0 or negative value to indicate not caching schema. - * If greater than 0 and the primary cache is enabled, the table metadata will be cached. - * @see schemaCachingExclude - */ - public $schemaCachingDuration = 0; - /** - * @var array list of tables whose metadata should NOT be cached. Defaults to empty array. - * @see schemaCachingDuration - */ - public $schemaCachingExclude = array(); - /** - * @var string the ID of the cache application component that is used to cache the table metadata. - * Defaults to 'cache' which refers to the primary cache application component. - * Set this property to false if you want to disable caching table metadata. - * @since 1.0.10 - */ - public $schemaCacheID = 'cache'; - /** - * @var integer number of seconds that query results can remain valid in cache. - * Use 0 or negative value to indicate not caching query results (the default behavior). - * - * In order to enable query caching, this property must be a positive - * integer and {@link queryCacheID} must point to a valid cache component ID. - * - * The method {@link cache()} is provided as a convenient way of setting this property - * and {@link queryCachingDependency} on the fly. - * - * @see cache - * @see queryCachingDependency - * @see queryCacheID - * @since 1.1.7 - */ - public $queryCachingDuration = 0; - /** - * @var CCacheDependency the dependency that will be used when saving query results into cache. - * @see queryCachingDuration - * @since 1.1.7 - */ - public $queryCachingDependency; - /** - * @var integer the number of SQL statements that need to be cached next. - * If this is 0, then even if query caching is enabled, no query will be cached. - * Note that each time after executing a SQL statement (whether executed on DB server or fetched from - * query cache), this property will be reduced by 1 until 0. - * @since 1.1.7 - */ - public $queryCachingCount = 0; - /** - * @var string the ID of the cache application component that is used for query caching. - * Defaults to 'cache' which refers to the primary cache application component. - * Set this property to false if you want to disable query caching. - * @since 1.1.7 - */ - public $queryCacheID = 'cache'; - /** - * @var boolean whether the database connection should be automatically established - * the component is being initialized. Defaults to true. Note, this property is only - * effective when the Connection object is used as an application component. - */ - public $autoConnect = true; - /** - * @var string the charset used for database connection. The property is only used - * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset - * as specified by the database. - * - * Note that if you're using GBK or BIG5 then it's highly recommended to - * update to PHP 5.3.6+ and to specify charset via DSN like - * 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. - */ - public $charset; - /** - * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO - * will use the native prepare support if available. For some databases (such as MySQL), - * this may need to be set true so that PDO can emulate the prepare support to bypass - * the buggy native prepare support. Note, this property is only effective for PHP 5.1.3 or above. - * The default value is null, which will not change the ATTR_EMULATE_PREPARES value of PDO. - */ - public $emulatePrepare; - /** - * @var boolean whether to log the values that are bound to a prepare SQL statement. - * Defaults to false. During development, you may consider setting this property to true - * so that parameter values bound to SQL statements are logged for debugging purpose. - * You should be aware that logging parameter values could be expensive and have significant - * impact on the performance of your application. - * @since 1.0.5 - */ - public $enableParamLogging = false; - /** - * @var boolean whether to enable profiling the SQL statements being executed. - * Defaults to false. This should be mainly enabled and used during development - * to find out the bottleneck of SQL executions. - * @since 1.0.6 - */ - public $enableProfiling = false; - /** - * @var string the default prefix for table names. Defaults to null, meaning no table prefix. - * By setting this property, any token like '{{tableName}}' in {@link CDbCommand::text} will - * be replaced by 'prefixTableName', where 'prefix' refers to this property value. - * @since 1.1.0 - */ - public $tablePrefix; - /** - * @var array list of SQL statements that should be executed right after the DB connection is established. - * @since 1.1.1 - */ - public $initSQLs; - /** - * @var array mapping between PDO driver and schema class name. - * A schema class can be specified using path alias. - * @since 1.1.6 - */ - public $driverMap = array( - 'pgsql' => 'CPgsqlSchema', // PostgreSQL - 'mysqli' => 'CMysqlSchema', // MySQL - 'mysql' => 'CMysqlSchema', // MySQL - 'sqlite' => 'CSqliteSchema', // sqlite 3 - 'sqlite2' => 'CSqliteSchema', // sqlite 2 - 'mssql' => 'CMssqlSchema', // Mssql driver on windows hosts - 'dblib' => 'CMssqlSchema', // dblib drivers on linux (and maybe others os) hosts - 'sqlsrv' => 'CMssqlSchema', // Mssql - 'oci' => 'COciSchema', // Oracle driver - ); - - /** - * @var string Custom PDO wrapper class. - * @since 1.1.8 - */ - public $pdoClass = 'PDO'; - - private $_attributes = array(); - private $_active = false; - private $_pdo; - private $_transaction; - private $_schema; - - - /** - * Constructor. - * Note, the DB connection is not established when this connection - * instance is created. Set {@link setActive active} property to true - * to establish the connection. - * @param string $dsn The Data Source Name, or DSN, contains the information required to connect to the database. - * @param string $username The user name for the DSN string. - * @param string $password The password for the DSN string. - * @see http://www.php.net/manual/en/function.PDO-construct.php - */ - public function __construct($dsn = '', $username = '', $password = '') - { - $this->connectionString = $dsn; - $this->username = $username; - $this->password = $password; - } - - /** - * Close the connection when serializing. - * @return array - */ - public function __sleep() - { - $this->close(); - return array_keys(get_object_vars($this)); - } - - /** - * Returns a list of available PDO drivers. - * @return array list of available PDO drivers - * @see http://www.php.net/manual/en/function.PDO-getAvailableDrivers.php - */ - public static function getAvailableDrivers() - { - return PDO::getAvailableDrivers(); - } - - /** - * Initializes the component. - * This method is required by {@link IApplicationComponent} and is invoked by application - * when the Connection is used as an application component. - * If you override this method, make sure to call the parent implementation - * so that the component can be marked as initialized. - */ - public function init() - { - parent::init(); - if ($this->autoConnect) - $this->setActive(true); - } - - /** - * Returns whether the DB connection is established. - * @return boolean whether the DB connection is established - */ - public function getActive() - { - return $this->_active; - } - - /** - * Open or close the DB connection. - * @param boolean $value whether to open or close DB connection - * @throws CException if connection fails - */ - public function setActive($value) - { - if ($value != $this->_active) - { - if ($value) - $this->open(); - else - $this->close(); - } - } - - /** - * Sets the parameters about query caching. - * This method can be used to enable or disable query caching. - * By setting the $duration parameter to be 0, the query caching will be disabled. - * Otherwise, query results of the new SQL statements executed next will be saved in cache - * and remain valid for the specified duration. - * If the same query is executed again, the result may be fetched from cache directly - * without actually executing the SQL statement. - * @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 Connection the connection instance itself. - * @since 1.1.7 - */ - public function cache($duration, $dependency = null, $queryCount = 1) - { - $this->queryCachingDuration = $duration; - $this->queryCachingDependency = $dependency; - $this->queryCachingCount = $queryCount; - return $this; - } - - /** - * Opens DB connection if it is currently not - * @throws CException if connection fails - */ - protected function open() - { - if ($this->_pdo === null) - { - if (empty($this->connectionString)) - throw new CDbException(Yii::t('yii', 'Connection.connectionString cannot be empty.')); - try - { - Yii::trace('Opening DB connection', 'system.db.Connection'); - $this->_pdo = $this->createPdoInstance(); - $this->initConnection($this->_pdo); - $this->_active = true; - } - catch(PDOException $e) - { - if (YII_DEBUG) - { - throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection: {error}', - array('{error}' => $e->getMessage())), (int)$e->getCode(), $e->errorInfo); - } - else - { - Yii::log($e->getMessage(), CLogger::LEVEL_ERROR, 'exception.CDbException'); - throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection.'), (int)$e->getCode(), $e->errorInfo); - } - } - } - } - - /** - * Closes the currently active DB connection. - * It does nothing if the connection is already closed. - */ - protected function close() - { - Yii::trace('Closing DB connection', 'system.db.Connection'); - $this->_pdo = null; - $this->_active = false; - $this->_schema = null; - } - - /** - * Creates the PDO instance. - * When some functionalities are missing in the pdo driver, we may use - * an adapter class to provides them. - * @return PDO the pdo instance - * @since 1.0.4 - */ - protected function createPdoInstance() - { - $pdoClass = $this->pdoClass; - if (($pos = strpos($this->connectionString, ':')) !== false) - { - $driver = strtolower(substr($this->connectionString, 0, $pos)); - if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') - $pdoClass = 'CMssqlPdoAdapter'; - } - return new $pdoClass($this->connectionString, $this->username, - $this->password, $this->_attributes); - } - - /** - * Initializes the open db connection. - * This method is invoked right after the db connection is established. - * The default implementation is to set the charset for MySQL and PostgreSQL database connections. - * @param PDO $pdo the PDO instance - */ - protected function initConnection($pdo) - { - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); - if ($this->charset !== null) - { - $driver = strtolower($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)); - if (in_array($driver, array('pgsql', 'mysql', 'mysqli'))) - $pdo->exec('SET NAMES ' . $pdo->quote($this->charset)); - } - if ($this->initSQLs !== null) - { - foreach ($this->initSQLs as $sql) - $pdo->exec($sql); - } - } - - /** - * Returns the PDO instance. - * @return PDO the PDO instance, null if the connection is not established yet - */ - public function getPdoInstance() - { - return $this->_pdo; - } - - /** - * Creates a command for execution. - * @param mixed $query the DB query to be executed. This can be either a string representing a SQL statement, - * or an array representing different fragments of a SQL statement. Please refer to {@link CDbCommand::__construct} - * for more details about how to pass an array as the query. If this parameter is not given, - * you will have to call query builder methods of {@link CDbCommand} to build the DB query. - * @return CDbCommand the DB command - */ - public function createCommand($query = null) - { - $this->setActive(true); - return new CDbCommand($this, $query); - } - - /** - * Returns the currently active transaction. - * @return CDbTransaction the currently active transaction. Null if no active transaction. - */ - public function getCurrentTransaction() - { - if ($this->_transaction !== null) - { - if ($this->_transaction->getActive()) - return $this->_transaction; - } - return null; - } - - /** - * Starts a transaction. - * @return CDbTransaction the transaction initiated - */ - public function beginTransaction() - { - Yii::trace('Starting transaction', 'system.db.Connection'); - $this->setActive(true); - $this->_pdo->beginTransaction(); - return $this->_transaction = new CDbTransaction($this); - } - - /** - * Returns the database schema for the current connection - * @return CDbSchema the database schema for the current connection - */ - public function getSchema() - { - if ($this->_schema !== null) - return $this->_schema; - else - { - $driver = $this->getDriverName(); - if (isset($this->driverMap[$driver])) - return $this->_schema = Yii::createComponent($this->driverMap[$driver], $this); - else - throw new CDbException(Yii::t('yii', 'Connection does not support reading schema for {driver} database.', - array('{driver}' => $driver))); - } - } - - /** - * Returns the SQL command builder for the current DB connection. - * @return CDbCommandBuilder the command builder - * @since 1.0.4 - */ - public function getCommandBuilder() - { - return $this->getSchema()->getCommandBuilder(); - } - - /** - * Returns the ID of the last inserted row or sequence value. - * @param string $sequenceName name of the sequence object (required by some DBMS) - * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object - * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php - */ - public function getLastInsertID($sequenceName = '') - { - $this->setActive(true); - return $this->_pdo->lastInsertId($sequenceName); - } - - /** - * Quotes a string value for use in a query. - * @param string $str string to be quoted - * @return string the properly quoted string - * @see http://www.php.net/manual/en/function.PDO-quote.php - */ - public function quoteValue($str) - { - if (is_int($str) || is_float($str)) - return $str; - - $this->setActive(true); - if (($value = $this->_pdo->quote($str)) !== false) - return $value; - else // the driver doesn't support quote (e.g. oci) - return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; - } - - /** - * Quotes a table name for use in a query. - * If the table name contains schema prefix, the prefix will also be properly quoted. - * @param string $name table name - * @return string the properly quoted table name - */ - public function quoteTableName($name) - { - return $this->getSchema()->quoteTableName($name); - } - - /** - * Quotes a column name for use in a query. - * If the column name contains prefix, the prefix will also be properly quoted. - * @param string $name column name - * @return string the properly quoted column name - */ - public function quoteColumnName($name) - { - return $this->getSchema()->quoteColumnName($name); - } - - /** - * Determines the PDO type for the specified PHP type. - * @param string $type The PHP type (obtained by gettype() call). - * @return integer the corresponding PDO type - */ - public function getPdoType($type) - { - static $map = array - ( - 'boolean' => PDO::PARAM_BOOL, - 'integer' => PDO::PARAM_INT, - 'string' => PDO::PARAM_STR, - 'NULL' => PDO::PARAM_NULL, - ); - return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR; - } - - /** - * Returns the case of the column names - * @return mixed the case of the column names - * @see http://www.php.net/manual/en/pdo.setattribute.php - */ - public function getColumnCase() - { - return $this->getAttribute(PDO::ATTR_CASE); - } - - /** - * Sets the case of the column names. - * @param mixed $value the case of the column names - * @see http://www.php.net/manual/en/pdo.setattribute.php - */ - public function setColumnCase($value) - { - $this->setAttribute(PDO::ATTR_CASE, $value); - } - - /** - * Returns how the null and empty strings are converted. - * @return mixed how the null and empty strings are converted - * @see http://www.php.net/manual/en/pdo.setattribute.php - */ - public function getNullConversion() - { - return $this->getAttribute(PDO::ATTR_ORACLE_NULLS); - } - - /** - * Sets how the null and empty strings are converted. - * @param mixed $value how the null and empty strings are converted - * @see http://www.php.net/manual/en/pdo.setattribute.php - */ - public function setNullConversion($value) - { - $this->setAttribute(PDO::ATTR_ORACLE_NULLS, $value); - } - - /** - * Returns whether creating or updating a DB record will be automatically committed. - * Some DBMS (such as sqlite) may not support this feature. - * @return boolean whether creating or updating a DB record will be automatically committed. - */ - public function getAutoCommit() - { - return $this->getAttribute(PDO::ATTR_AUTOCOMMIT); - } - - /** - * Sets whether creating or updating a DB record will be automatically committed. - * Some DBMS (such as sqlite) may not support this feature. - * @param boolean $value whether creating or updating a DB record will be automatically committed. - */ - public function setAutoCommit($value) - { - $this->setAttribute(PDO::ATTR_AUTOCOMMIT, $value); - } - - /** - * Returns whether the connection is persistent or not. - * Some DBMS (such as sqlite) may not support this feature. - * @return boolean whether the connection is persistent or not - */ - public function getPersistent() - { - return $this->getAttribute(PDO::ATTR_PERSISTENT); - } - - /** - * Sets whether the connection is persistent or not. - * Some DBMS (such as sqlite) may not support this feature. - * @param boolean $value whether the connection is persistent or not - */ - public function setPersistent($value) - { - return $this->setAttribute(PDO::ATTR_PERSISTENT, $value); - } - - /** - * Returns the name of the DB driver - * @return string name of the DB driver - */ - public function getDriverName() - { - if (($pos = strpos($this->connectionString, ':')) !== false) - return strtolower(substr($this->connectionString, 0, $pos)); - // return $this->getAttribute(PDO::ATTR_DRIVER_NAME); - } - - /** - * Returns the version information of the DB driver. - * @return string the version information of the DB driver - */ - public function getClientVersion() - { - return $this->getAttribute(PDO::ATTR_CLIENT_VERSION); - } - - /** - * Returns the status of the connection. - * Some DBMS (such as sqlite) may not support this feature. - * @return string the status of the connection - */ - public function getConnectionStatus() - { - return $this->getAttribute(PDO::ATTR_CONNECTION_STATUS); - } - - /** - * Returns whether the connection performs data prefetching. - * @return boolean whether the connection performs data prefetching - */ - public function getPrefetch() - { - return $this->getAttribute(PDO::ATTR_PREFETCH); - } - - /** - * Returns the information of DBMS server. - * @return string the information of DBMS server - */ - public function getServerInfo() - { - return $this->getAttribute(PDO::ATTR_SERVER_INFO); - } - - /** - * Returns the version information of DBMS server. - * @return string the version information of DBMS server - */ - public function getServerVersion() - { - return $this->getAttribute(PDO::ATTR_SERVER_VERSION); - } - - /** - * Returns the timeout settings for the connection. - * @return integer timeout settings for the connection - */ - public function getTimeout() - { - return $this->getAttribute(PDO::ATTR_TIMEOUT); - } - - /** - * Obtains a specific DB connection attribute information. - * @param integer $name the attribute to be queried - * @return mixed the corresponding attribute information - * @see http://www.php.net/manual/en/function.PDO-getAttribute.php - */ - public function getAttribute($name) - { - $this->setActive(true); - return $this->_pdo->getAttribute($name); - } - - /** - * Sets an attribute on the database connection. - * @param integer $name the attribute to be set - * @param mixed $value the attribute value - * @see http://www.php.net/manual/en/function.PDO-setAttribute.php - */ - public function setAttribute($name, $value) - { - if ($this->_pdo instanceof PDO) - $this->_pdo->setAttribute($name, $value); - else - $this->_attributes[$name] = $value; - } - - /** - * Returns the attributes that are previously explicitly set for the DB connection. - * @return array attributes (name=>value) that are previously explicitly set for the DB connection. - * @see setAttributes - * @since 1.1.7 - */ - public function getAttributes() - { - return $this->_attributes; - } - - /** - * Sets a set of attributes on the database connection. - * @param array $values attributes (name=>value) to be set. - * @see setAttribute - * @since 1.1.7 - */ - public function setAttributes($values) - { - foreach ($values as $name => $value) - $this->_attributes[$name] = $value; - } - - /** - * Returns the statistical results of SQL executions. - * The results returned include the number of SQL statements executed and - * the total time spent. - * In order to use this method, {@link enableProfiling} has to be set true. - * @return array the first element indicates the number of SQL statements executed, - * and the second element the total time spent in SQL execution. - * @since 1.0.6 - */ - public function getStats() - { - $logger = Yii::getLogger(); - $timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.query'); - $count = count($timings); - $time = array_sum($timings); - $timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.execute'); - $count += count($timings); - $time += array_sum($timings); - return array($count, $time); - } -} diff --git a/framework/db/pdo/DataReader.php b/framework/db/pdo/DataReader.php deleted file mode 100644 index b62a08b..0000000 --- a/framework/db/pdo/DataReader.php +++ /dev/null @@ -1,241 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CDbDataReader represents a forward-only stream of rows from a query result set. - * - * To read the current row of data, call {@link read}. The method {@link readAll} - * returns all the rows in a single array. - * - * One can also retrieve the rows of data in CDbDataReader by using foreach: - *
- * foreach($reader as $row)
- *     // $row represents a row of data
- * 
- * Since CDbDataReader is a forward-only stream, you can only traverse it once. - * - * It is possible to use a specific mode of data fetching by setting - * {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php} - * for more details. - * - * @author Qiang Xue - * @version $Id: CDbDataReader.php 3204 2011-05-05 21:36:32Z alexander.makarow $ - * @package system.db - * @since 1.0 - */ -class CDbDataReader extends CComponent implements Iterator, Countable -{ - private $_statement; - private $_closed = false; - private $_row; - private $_index = -1; - - /** - * Constructor. - * @param CDbCommand $command the command generating the query result - */ - public function __construct(CDbCommand $command) - { - $this->_statement = $command->getPdoStatement(); - $this->_statement->setFetchMode(PDO::FETCH_ASSOC); - } - - /** - * Binds a column to a PHP variable. - * When rows of data are being fetched, the corresponding column value - * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND. - * @param mixed $column Number of the column (1-indexed) or name of the column - * in the result set. If using the column name, be aware that the name - * should match the case of the column, as returned by the driver. - * @param mixed $value Name of the PHP variable to which the column will be bound. - * @param integer $dataType Data type of the parameter - * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php - */ - public function bindColumn($column, &$value, $dataType = null) - { - if ($dataType === null) - $this->_statement->bindColumn($column, $value); - else - $this->_statement->bindColumn($column, $value, $dataType); - } - - /** - * Set the default fetch mode for this statement - * @param mixed $mode fetch mode - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - */ - public function setFetchMode($mode) - { - $params = func_get_args(); - call_user_func_array(array($this->_statement, 'setFetchMode'), $params); - } - - /** - * Advances the reader to the next row in a result set. - * @return array|false the current row, false if no more row available - */ - public function read() - { - return $this->_statement->fetch(); - } - - /** - * Returns a single column from the next row of a result set. - * @param integer $columnIndex zero-based column index - * @return mixed|false the column of the current row, false if no more row available - */ - public function readColumn($columnIndex) - { - return $this->_statement->fetchColumn($columnIndex); - } - - /** - * Returns an object populated with the next row of data. - * @param string $className class name of the object to be created and populated - * @param array $fields Elements of this array are passed to the constructor - * @return mixed|false the populated object, false if no more row of data available - */ - public function readObject($className, $fields) - { - return $this->_statement->fetchObject($className, $fields); - } - - /** - * Reads the whole result set into an array. - * @return array the result set (each array element represents a row of data). - * An empty array will be returned if the result contains no row. - */ - public function readAll() - { - return $this->_statement->fetchAll(); - } - - /** - * Advances the reader to the next result when reading the results of a batch of statements. - * This method is only useful when there are multiple result sets - * returned by the query. Not all DBMS support this feature. - * @return boolean Returns true on success or false on failure. - */ - public function nextResult() - { - if (($result = $this->_statement->nextRowset()) !== false) - $this->_index = -1; - return $result; - } - - /** - * Closes the reader. - * This frees up the resources allocated for executing this SQL statement. - * Read attemps after this method call are unpredictable. - */ - public function close() - { - $this->_statement->closeCursor(); - $this->_closed = true; - } - - /** - * whether the reader is closed or not. - * @return boolean whether the reader is closed or not. - */ - public function getIsClosed() - { - return $this->_closed; - } - - /** - * Returns the number of rows in the result set. - * Note, most DBMS may not give a meaningful count. - * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. - * @return integer number of rows contained in the result. - */ - public function getRowCount() - { - return $this->_statement->rowCount(); - } - - /** - * Returns the number of rows in the result set. - * This method is required by the Countable interface. - * Note, most DBMS may not give a meaningful count. - * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. - * @return integer number of rows contained in the result. - */ - public function count() - { - return $this->getRowCount(); - } - - /** - * Returns the number of columns in the result set. - * Note, even there's no row in the reader, this still gives correct column number. - * @return integer the number of columns in the result set. - */ - public function getColumnCount() - { - return $this->_statement->columnCount(); - } - - /** - * Resets the iterator to the initial state. - * This method is required by the interface Iterator. - * @throws CException if this method is invoked twice - */ - public function rewind() - { - if ($this->_index < 0) - { - $this->_row = $this->_statement->fetch(); - $this->_index = 0; - } - else - throw new CDbException(Yii::t('yii', 'CDbDataReader cannot rewind. It is a forward-only reader.')); - } - - /** - * Returns the index of the current row. - * This method is required by the interface Iterator. - * @return integer the index of the current row. - */ - public function key() - { - return $this->_index; - } - - /** - * Returns the current row. - * This method is required by the interface Iterator. - * @return mixed the current row. - */ - public function current() - { - return $this->_row; - } - - /** - * Moves the internal pointer to the next row. - * This method is required by the interface Iterator. - */ - public function next() - { - $this->_row = $this->_statement->fetch(); - $this->_index++; - } - - /** - * Returns whether there is a row of data at current position. - * This method is required by the interface Iterator. - * @return boolean whether there is a row of data at current position. - */ - public function valid() - { - return $this->_row !== false; - } -} diff --git a/framework/db/pdo/Transaction.php b/framework/db/pdo/Transaction.php deleted file mode 100644 index 0d6a9c6..0000000 --- a/framework/db/pdo/Transaction.php +++ /dev/null @@ -1,108 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CDbTransaction represents a DB transaction. - * - * It is usually created by calling {@link CDbConnection::beginTransaction}. - * - * The following code is a common scenario of using transactions: - *
- * $transaction=$connection->beginTransaction();
- * try
- * {
- *    $connection->createCommand($sql1)->execute();
- *    $connection->createCommand($sql2)->execute();
- *    //.... other SQL executions
- *    $transaction->commit();
- * }
- * catch(Exception $e)
- * {
- *    $transaction->rollBack();
- * }
- * 
- * - * @author Qiang Xue - * @version $Id: CDbTransaction.php 3069 2011-03-14 00:28:38Z qiang.xue $ - * @package system.db - * @since 1.0 - */ -class CDbTransaction extends CComponent -{ - private $_connection = null; - private $_active; - - /** - * Constructor. - * @param CDbConnection $connection the connection associated with this transaction - * @see CDbConnection::beginTransaction - */ - public function __construct(CDbConnection $connection) - { - $this->_connection = $connection; - $this->_active = true; - } - - /** - * Commits a transaction. - * @throws CException if the transaction or the DB connection is not active. - */ - public function commit() - { - if ($this->_active && $this->_connection->getActive()) - { - Yii::trace('Committing transaction', 'system.db.CDbTransaction'); - $this->_connection->getPdoInstance()->commit(); - $this->_active = false; - } - else - throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.')); - } - - /** - * Rolls back a transaction. - * @throws CException if the transaction or the DB connection is not active. - */ - public function rollback() - { - if ($this->_active && $this->_connection->getActive()) - { - Yii::trace('Rolling back transaction', 'system.db.CDbTransaction'); - $this->_connection->getPdoInstance()->rollBack(); - $this->_active = false; - } - else - throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.')); - } - - /** - * @return CDbConnection the DB connection for this transaction - */ - public function getConnection() - { - return $this->_connection; - } - - /** - * @return boolean whether this transaction is active - */ - public function getActive() - { - return $this->_active; - } - - /** - * @param boolean $value whether this transaction is active - */ - protected function setActive($value) - { - $this->_active = $value; - } -} diff --git a/todo.txt b/todo.txt index f9d7329..1f23e83 100644 --- a/todo.txt +++ b/todo.txt @@ -5,6 +5,7 @@ * application * http exception - validators + * type conversion rules * CompareValidator::clientValidateAttribute(): search for "CHtml::activeId" * FileValidator, UniqueValidator, ExistValidator, DateValidator: TBD * consider merging UniqueValidator and ExistValidator and using a NOT property.