From 8517ee9e501810402065431d2a7167132978450e Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 5 Jan 2013 16:46:53 -0500 Subject: [PATCH] reorganized DB classes. --- docs/model.md | 6 +- framework/base/Application.php | 4 +- framework/base/Module.php | 2 +- framework/caching/DbCache.php | 4 +- framework/caching/DbDependency.php | 4 +- .../create/default/protected/config/main.php | 2 +- framework/db/ActiveQuery.php | 273 ++++++ framework/db/ActiveRecord.php | 926 ++++++++++++++++++++ framework/db/ActiveRecordBehavior.php | 102 +++ framework/db/ActiveRelation.php | 269 ++++++ framework/db/BaseQuery.php | 555 ++++++++++++ framework/db/ColumnSchema.php | 127 +++ framework/db/Command.php | 424 +++++++++ framework/db/Connection.php | 623 ++++++++++++++ framework/db/DataReader.php | 256 ++++++ framework/db/Driver.php | 283 ++++++ framework/db/Expression.php | 62 ++ framework/db/Query.php | 310 +++++++ framework/db/QueryBuilder.php | 947 +++++++++++++++++++++ framework/db/TableSchema.php | 109 +++ framework/db/Transaction.php | 91 ++ framework/db/ar/ActiveQuery.php | 273 ------ framework/db/ar/ActiveRecord.php | 926 -------------------- framework/db/ar/ActiveRecordBehavior.php | 102 --- framework/db/ar/ActiveRelation.php | 269 ------ framework/db/dao/BaseQuery.php | 555 ------------ framework/db/dao/ColumnSchema.php | 127 --- framework/db/dao/Command.php | 424 --------- framework/db/dao/Connection.php | 623 -------------- framework/db/dao/DataReader.php | 256 ------ framework/db/dao/Driver.php | 283 ------ framework/db/dao/Expression.php | 62 -- framework/db/dao/Query.php | 310 ------- framework/db/dao/QueryBuilder.php | 947 --------------------- framework/db/dao/TableSchema.php | 109 --- framework/db/dao/Transaction.php | 91 -- framework/db/dao/mysql/Driver.php | 269 ------ framework/db/dao/mysql/QueryBuilder.php | 90 -- framework/db/dao/sqlite/Driver.php | 203 ----- framework/db/dao/sqlite/QueryBuilder.php | 149 ---- framework/db/mysql/Driver.php | 269 ++++++ framework/db/mysql/QueryBuilder.php | 90 ++ framework/db/sqlite/Driver.php | 203 +++++ framework/db/sqlite/QueryBuilder.php | 149 ++++ framework/logging/DbTarget.php | 8 +- framework/logging/Logger.php | 2 +- framework/logging/Target.php | 4 +- framework/validators/ExistValidator.php | 12 +- framework/validators/UniqueValidator.php | 6 +- tests/unit/MysqlTestCase.php | 4 +- tests/unit/data/ar/ActiveRecord.php | 4 +- tests/unit/data/ar/Customer.php | 2 +- tests/unit/framework/db/ar/ActiveRecordTest.php | 4 +- tests/unit/framework/db/dao/CommandTest.php | 8 +- tests/unit/framework/db/dao/ConnectionTest.php | 2 +- tests/unit/framework/db/dao/QueryTest.php | 8 +- upgrade.md | 4 +- 57 files changed, 6113 insertions(+), 6113 deletions(-) create mode 100644 framework/db/ActiveQuery.php create mode 100644 framework/db/ActiveRecord.php create mode 100644 framework/db/ActiveRecordBehavior.php create mode 100644 framework/db/ActiveRelation.php create mode 100644 framework/db/BaseQuery.php create mode 100644 framework/db/ColumnSchema.php create mode 100644 framework/db/Command.php create mode 100644 framework/db/Connection.php create mode 100644 framework/db/DataReader.php create mode 100644 framework/db/Driver.php create mode 100644 framework/db/Expression.php create mode 100644 framework/db/Query.php create mode 100644 framework/db/QueryBuilder.php create mode 100644 framework/db/TableSchema.php create mode 100644 framework/db/Transaction.php delete mode 100644 framework/db/ar/ActiveQuery.php delete mode 100644 framework/db/ar/ActiveRecord.php delete mode 100644 framework/db/ar/ActiveRecordBehavior.php delete mode 100644 framework/db/ar/ActiveRelation.php delete mode 100644 framework/db/dao/BaseQuery.php delete mode 100644 framework/db/dao/ColumnSchema.php delete mode 100644 framework/db/dao/Command.php delete mode 100644 framework/db/dao/Connection.php delete mode 100644 framework/db/dao/DataReader.php delete mode 100644 framework/db/dao/Driver.php delete mode 100644 framework/db/dao/Expression.php delete mode 100644 framework/db/dao/Query.php delete mode 100644 framework/db/dao/QueryBuilder.php delete mode 100644 framework/db/dao/TableSchema.php delete mode 100644 framework/db/dao/Transaction.php delete mode 100644 framework/db/dao/mysql/Driver.php delete mode 100644 framework/db/dao/mysql/QueryBuilder.php delete mode 100644 framework/db/dao/sqlite/Driver.php delete mode 100644 framework/db/dao/sqlite/QueryBuilder.php create mode 100644 framework/db/mysql/Driver.php create mode 100644 framework/db/mysql/QueryBuilder.php create mode 100644 framework/db/sqlite/Driver.php create mode 100644 framework/db/sqlite/QueryBuilder.php diff --git a/docs/model.md b/docs/model.md index e9e0d62..1c34dd2 100644 --- a/docs/model.md +++ b/docs/model.md @@ -20,7 +20,7 @@ A model should list all its available attributes in the `attributes()` method. Attributes may be implemented in various ways. The [[\yii\base\Model]] class implements attributes as public member variables of the class, while the -[[\yii\db\ar\ActiveRecord]] class implements them as DB table columns. For example, +[[\yii\db\ActiveRecord]] class implements them as DB table columns. For example, ~~~php // LoginForm has two attributes: username and password @@ -32,7 +32,7 @@ class LoginForm extends \yii\base\Model // Post is associated with the tbl_post DB table. // Its attributes correspond to the columns in tbl_post -class Post extends \yii\db\ar\ActiveRecord +class Post extends \yii\db\ActiveRecord { public function table() { @@ -65,7 +65,7 @@ whose keys are the scenario names and whose values are the corresponding active attribute lists. Below is an example: ~~~php -class User extends \yii\db\ar\ActiveRecord +class User extends \yii\db\ActiveRecord { public function table() { diff --git a/framework/base/Application.php b/framework/base/Application.php index b38b2a2..fbd0c63 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -52,7 +52,7 @@ use yii\base\Exception; * @property CCache $cache Returns the cache component. * @property CPhpMessageSource $coreMessages Returns the core message translations. * @property CDateFormatter $dateFormatter Returns the locale-dependent date formatter. - * @property \yii\db\dao\Connection $db Returns the database connection component. + * @property \yii\db\Connection $db Returns the database connection component. * @property CErrorHandler $errorHandler Returns the error handler component. * @property string $extensionPath Returns the root directory that holds all third-party extensions. * @property string $id Returns the unique identifier for the application. @@ -324,7 +324,7 @@ class Application extends Module /** * Returns the database connection component. - * @return \yii\db\dao\Connection the database connection + * @return \yii\db\Connection the database connection */ public function getDb() { diff --git a/framework/base/Module.php b/framework/base/Module.php index 07005fb..7b00ac7 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -501,7 +501,7 @@ abstract class Module extends Component * ~~~ * array( * 'db' => array( - * 'class' => 'yii\db\dao\Connection', + * 'class' => 'yii\db\Connection', * 'dsn' => 'sqlite:path/to/file.db', * ), * 'cache' => array( diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php index 2d8b4fd..0eea7d5 100644 --- a/framework/caching/DbCache.php +++ b/framework/caching/DbCache.php @@ -10,8 +10,8 @@ namespace yii\caching; use yii\base\Exception; -use yii\db\dao\Connection; -use yii\db\dao\Query; +use yii\db\Connection; +use yii\db\Query; /** * DbCache implements a cache application component by storing cached data in a database. diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php index 319483e..1b252ea 100644 --- a/framework/caching/DbDependency.php +++ b/framework/caching/DbDependency.php @@ -10,8 +10,8 @@ namespace yii\caching; use yii\base\Exception; -use yii\db\dao\Connection; -use yii\db\dao\Query; +use yii\db\Connection; +use yii\db\Query; /** * DbDependency represents a dependency based on the query result of a SQL statement. diff --git a/framework/console/create/default/protected/config/main.php b/framework/console/create/default/protected/config/main.php index f0ca5be..1e3f981 100644 --- a/framework/console/create/default/protected/config/main.php +++ b/framework/console/create/default/protected/config/main.php @@ -6,7 +6,7 @@ return array( // uncomment the following to use a MySQL database /* 'db' => array( - 'class' => 'yii\db\dao\Connection', + 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=testdrive', 'username' => 'root', 'password' => '', diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php new file mode 100644 index 0000000..3f534f4 --- /dev/null +++ b/framework/db/ActiveQuery.php @@ -0,0 +1,273 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +use yii\db\Connection; +use yii\db\Command; +use yii\db\QueryBuilder; +use yii\db\BaseQuery; +use yii\base\VectorIterator; +use yii\db\Expression; +use yii\db\Exception; + +class ActiveQuery extends BaseQuery +{ + /** + * @var string the name of the ActiveRecord class. + */ + public $modelClass; + /** + * @var array list of relations that this query should be performed with + */ + public $with; + /** + * @var string the name of the column by which the query result should be indexed. + * This is only used when the query result is returned as an array when calling [[all()]]. + */ + public $indexBy; + /** + * @var boolean whether to return each record as an array. If false (default), an object + * of [[modelClass]] will be created to represent each record. + */ + public $asArray; + /** + * @var array list of scopes that should be applied to this query + */ + public $scopes; + /** + * @var string the SQL statement to be executed for retrieving AR records. + * This is set by [[ActiveRecord::findBySql()]]. + */ + public $sql; + + public function __call($name, $params) + { + if (method_exists($this->modelClass, $name)) { + $this->scopes[$name] = $params; + return $this; + } else { + return parent::__call($name, $params); + } + } + + /** + * Executes query and returns all results as an array. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all() + { + $command = $this->createCommand(); + $rows = $command->queryAll(); + if ($rows !== array()) { + $models = $this->createModels($rows); + if (!empty($this->with)) { + $this->fetchRelatedModels($models, $this->with); + } + return $models; + } else { + return array(); + } + } + + /** + * Executes query and returns a single row of result. + * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], + * the query result may be either an array or an ActiveRecord object. Null will be returned + * if the query results in nothing. + */ + public function one() + { + $command = $this->createCommand(); + $row = $command->queryRow(); + if ($row !== false && !$this->asArray) { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + $model = $class::create($row); + if (!empty($this->with)) { + $models = array($model); + $this->fetchRelatedModels($models, $this->with); + $model = $models[0]; + } + return $model; + } else { + return $row === false ? null : $row; + } + } + + /** + * Returns a scalar value for this query. + * The value returned will be the first column in the first row of the query results. + * @return string|boolean the value of the first column in the first row of the query result. + * False is returned if there is no value. + */ + public function value() + { + return $this->createCommand()->queryScalar(); + } + + /** + * Executes query and returns if matching row exists in the table. + * @return bool if row exists in the table. + */ + public function exists() + { + $this->select = array(new Expression('1')); + return $this->value() !== false; + } + + /** + * Creates a DB command that can be used to execute this query. + * @return Command the created DB command instance. + */ + public function createCommand() + { + /** @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + $db = $modelClass::getDbConnection(); + if ($this->sql === null) { + if ($this->from === null) { + $tableName = $modelClass::tableName(); + $this->from = array($tableName); + } + if (!empty($this->scopes)) { + foreach ($this->scopes as $name => $config) { + if (is_integer($name)) { + $modelClass::$config($this); + } else { + array_unshift($config, $this); + call_user_func_array(array($modelClass, $name), $config); + } + } + } + /** @var $qb QueryBuilder */ + $qb = $db->getQueryBuilder(); + $this->sql = $qb->build($this); + } + return $db->createCommand($this->sql, $this->params); + } + + public function asArray($value = true) + { + $this->asArray = $value; + return $this; + } + + public function with() + { + $this->with = func_get_args(); + if (isset($this->with[0]) && is_array($this->with[0])) { + // the parameter is given as an array + $this->with = $this->with[0]; + } + return $this; + } + + public function indexBy($column) + { + $this->indexBy = $column; + return $this; + } + + public function scopes($names) + { + $this->scopes = $names; + return $this; + } + + protected function createModels($rows) + { + $models = array(); + if ($this->asArray) { + if ($this->indexBy === null) { + return $rows; + } + foreach ($rows as $row) { + $models[$row[$this->indexBy]] = $row; + } + } else { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + if ($this->indexBy === null) { + foreach ($rows as $row) { + $models[] = $class::create($row); + } + } else { + foreach ($rows as $row) { + $model = $class::create($row); + $models[$model->{$this->indexBy}] = $model; + } + } + } + return $models; + } + + protected function fetchRelatedModels(&$models, $with) + { + $primaryModel = new $this->modelClass; + $relations = $this->normalizeRelations($primaryModel, $with); + foreach ($relations as $name => $relation) { + if ($relation->asArray === null) { + // inherit asArray from primary query + $relation->asArray = $this->asArray; + } + $relation->findWith($name, $models); + } + } + + /** + * @param ActiveRecord $model + * @param array $with + * @return ActiveRelation[] + * @throws \yii\db\Exception + */ + protected function normalizeRelations($model, $with) + { + $relations = array(); + foreach ($with as $name => $options) { + if (is_integer($name)) { + $name = $options; + $options = array(); + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + if (!isset($relations[$name])) { + if (!method_exists($model, $name)) { + throw new Exception("Unknown relation: $name"); + } + /** @var $relation ActiveRelation */ + $relation = $model->$name(); + $relation->primaryModel = null; + $relations[$name] = $relation; + } else { + $relation = $relations[$name]; + } + + if (isset($childName)) { + if (isset($relation->with[$childName])) { + $relation->with[$childName] = array_merge($relation->with, $options); + } else { + $relation->with[$childName] = $options; + } + } else { + foreach ($options as $p => $v) { + $relation->$p = $v; + } + } + } + return $relations; + } +} diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php new file mode 100644 index 0000000..2424446 --- /dev/null +++ b/framework/db/ActiveRecord.php @@ -0,0 +1,926 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +use yii\base\Model; +use yii\base\Event; +use yii\base\ModelEvent; +use yii\db\Exception; +use yii\db\Connection; +use yii\db\TableSchema; +use yii\db\Query; +use yii\db\Expression; +use yii\util\StringHelper; + +/** + * ActiveRecord is the base class for classes representing relational data. + * + * @author Qiang Xue + * @since 2.0 + * + * @property array $attributes attribute values indexed by attribute names + * + * ActiveRecord provides a set of events for further customization: + * + * - `beforeInsert`. Raised before the record is saved. + * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped. + * - `afterInsert`. Raised after the record is saved. + * - `beforeUpdate`. Raised before the record is saved. + * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped. + * - `afterUpdate`. Raised after the record is saved. + * - `beforeDelete`. Raised before the record is deleted. + * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped. + * - `afterDelete`. Raised after the record is deleted. + * + */ +abstract class ActiveRecord extends Model +{ + /** + * @var array attribute values indexed by attribute names + */ + private $_attributes = array(); + /** + * @var array old attribute values indexed by attribute names. + */ + private $_oldAttributes; + /** + * @var array related models indexed by the relation names + */ + private $_related; + + + /** + * Returns the database connection used by this AR class. + * By default, the "db" application component is used as the database connection. + * You may override this method if you want to use a different database connection. + * @return Connection the database connection used by this AR class. + */ + public static function getDbConnection() + { + return \Yii::$application->getDb(); + } + + /** + * Creates an [[ActiveQuery]] instance for query purpose. + * + * Because [[ActiveQuery]] implements a set of query building methods, + * additional query conditions can be specified by calling the methods of [[ActiveQuery]]. + * + * Below are some examples: + * + * ~~~ + * // find all customers + * $customers = Customer::find()->all(); + * // find a single customer whose primary key value is 10 + * $customer = Customer::find(10); + * // the above is equivalent to: + * Customer::find()->where(array('id' => 10))->one(); + * // find all active customers and order them by their age: + * $customers = Customer::find() + * ->where(array('status' => 1)) + * ->orderBy('age') + * ->all(); + * // or alternatively: + * $customers = Customer::find(array( + * 'where' => array('status' => 1), + * 'orderBy' => 'age', + * ))->all(); + * ~~~ + * + * @param mixed $q the query parameter. This can be one of the followings: + * + * - a scalar value (integer or string): query by a single primary key value and return the + * corresponding record. + * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object. + * + * @return ActiveQuery|ActiveRecord|boolean the [[ActiveQuery]] instance for query purpose, or + * the ActiveRecord object when a scalar is passed to this method which is considered to be a + * primary key value (false will be returned if no record is found in this case.) + */ + public static function find($q = null) + { + $query = static::createQuery(); + if (is_array($q)) { + foreach ($q as $name => $value) { + $query->$name = $value; + } + } elseif ($q !== null) { + // query by primary key + $primaryKey = static::primaryKey(); + return $query->where(array($primaryKey[0] => $q))->one(); + } + return $query; + } + + /** + * Creates an [[ActiveQuery]] instance and queries by a given SQL statement. + * Note that because the SQL statement is already specified, calling further + * query methods (such as `where()`, `order()`) on [[ActiveQuery]] will have no effect. + * Methods such as `with()`, `asArray()` can still be called though. + * @param string $sql the SQL statement to be executed + * @param array $params parameters to be bound to the SQL statement during execution. + * @return ActiveQuery the [[ActiveQuery]] instance + */ + public static function findBySql($sql, $params = array()) + { + $query = static::createQuery(); + $query->sql = $sql; + return $query->params($params); + } + + /** + * Performs a COUNT query for this AR class. + * + * Below are some usage examples: + * + * ~~~ + * // count the total number of customers + * echo Customer::count()->value(); + * // count the number of active customers: + * echo Customer::count(array( + * 'where' => array('status' => 1), + * ))->value(); + * // equivalent usage: + * echo Customer::count() + * ->where(array('status' => 1)) + * ->value(); + * // customize the count option + * echo Customer::count('COUNT(DISTINCT age)')->value(); + * ~~~ + * + * @param array|string $q the query option. This can be one of the followings: + * + * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object. + * - a string: the count expression, e.g. 'COUNT(DISTINCT age)'. + * + * @return ActiveQuery the [[ActiveQuery]] instance + */ + public static function count($q = null) + { + $query = static::createQuery(); + if (is_array($q)) { + foreach ($q as $name => $value) { + $query->$name = $value; + } + } elseif ($q !== null) { + $query->select = array($q); + } + if ($query->select === null) { + $query->select = array('COUNT(*)'); + } + return $query; + } + + /** + * Updates the whole table using the provided attribute values and conditions. + * @param array $attributes attribute values to be saved into the table + * @param string|array $condition the conditions that will be put in the WHERE part. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = '', $params = array()) + { + $query = new Query; + $query->update(static::tableName(), $attributes, $condition, $params); + return $query->createCommand(static::getDbConnection())->execute(); + } + + /** + * Updates the whole table using the provided counter values and conditions. + * @param array $counters the counters to be updated (attribute name => increment value). + * @param string|array $condition the conditions that will be put in the WHERE part. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function updateAllCounters($counters, $condition = '', $params = array()) + { + $db = static::getDbConnection(); + foreach ($counters as $name => $value) { + $value = (int)$value; + $quotedName = $db->quoteColumnName($name, true); + $counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value"); + } + $query = new Query; + $query->update(static::tableName(), $counters, $condition, $params); + return $query->createCommand($db)->execute(); + } + + /** + * Deletes rows in the table using the provided conditions. + * @param string|array $condition the conditions that will be put in the WHERE part. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function deleteAll($condition = '', $params = array()) + { + $query = new Query; + $query->delete(static::tableName(), $condition, $params); + return $query->createCommand(static::getDbConnection())->execute(); + } + + /** + * Creates a [[ActiveQuery]] instance. + * This method is called by [[find()]] and [[findBySql()]] to start a SELECT query. + * @return ActiveQuery the newly created [[ActiveQuery]] instance. + */ + public static function createQuery() + { + return new ActiveQuery(array('modelClass' => get_called_class())); + } + + /** + * Declares the name of the database table associated with this AR class. + * By default this method returns the class name as the table name by calling [[StringHelper::camel2id()]]. + * For example, 'Customer' becomes 'customer', and 'OrderDetail' becomes 'order_detail'. + * You may override this method if the table is not named after this convention. + * @return string the table name + */ + public static function tableName() + { + return StringHelper::camel2id(basename(get_called_class()), '_'); + } + + /** + * Returns the schema information of the DB table associated with this AR class. + * @return TableSchema the schema information of the DB table associated with this AR class. + */ + public static function getTableSchema() + { + return static::getDbConnection()->getTableSchema(static::tableName()); + } + + /** + * Returns the primary keys for this AR class. + * The default implementation will return the primary keys as declared + * in the DB table that is associated with this AR class. + * If the DB table does not declare any primary key, you should override + * this method to return the attributes that you want to use as primary keys + * for this AR class. + * @return string[] the primary keys of the associated database table. + */ + public static function primaryKey() + { + return static::getTableSchema()->primaryKey; + } + + /** + * Returns the default named scope that should be implicitly applied to all queries for this model. + * Note, the default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries. + * The default implementation simply returns an empty array. You may override this method + * if the model needs to be queried with some default criteria (e.g. only non-deleted users should be returned). + * @param ActiveQuery + */ + public static function defaultScope($query) + { + // todo: should we drop this? + } + + /** + * PHP getter magic method. + * This method is overridden so that attributes and related objects can be accessed like properties. + * @param string $name property name + * @return mixed property value + * @see getAttribute + */ + public function __get($name) + { + if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { + return $this->_attributes[$name]; + } elseif (isset($this->getTableSchema()->columns[$name])) { + return null; + } elseif (method_exists($this, $name)) { + // related records + if (isset($this->_related[$name]) || isset($this->_related) && array_key_exists($name, $this->_related)) { + return $this->_related[$name]; + } else { + // lazy loading related records + /** @var $relation ActiveRelation */ + $relation = $this->$name(); + return $this->_related[$name] = $relation->multiple ? $relation->all() : $relation->one(); + } + } else { + return parent::__get($name); + } + } + + /** + * PHP setter magic method. + * This method is overridden so that AR attributes can be accessed like properties. + * @param string $name property name + * @param mixed $value property value + */ + public function __set($name, $value) + { + if (isset($this->getTableSchema()->columns[$name])) { + $this->_attributes[$name] = $value; + } elseif (method_exists($this, $name)) { + $this->_related[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking + * if the named attribute is null or not. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + if (isset($this->_attributes[$name]) || isset($this->_related[$name])) { + return true; + } elseif (isset($this->getTableSchema()->columns[$name]) || method_exists($this, $name)) { + return false; + } else { + return parent::__isset($name); + } + } + + /** + * Sets a component property to be null. + * This method overrides the parent implementation by clearing + * the specified attribute value. + * @param string $name the property name or the event name + */ + public function __unset($name) + { + if (isset($this->getTableSchema()->columns[$name])) { + unset($this->_attributes[$name]); + } elseif (method_exists($this, $name)) { + unset($this->_related[$name]); + } else { + parent::__unset($name); + } + } + + /** + * Declares a `has-one` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the columns in the table associated with the `$class` model, while the values of the + * array refer to the corresponding columns in the table associated with this AR class. + * @param array $properties additional property values that should be used to + * initialize the newly created relation object. + * @return ActiveRelation the relation object. + */ + public function hasOne($class, $link, $properties = array()) + { + if (strpos($class, '\\') === false) { + $primaryClass = get_class($this); + if (($pos = strrpos($primaryClass, '\\')) !== false) { + $class = substr($primaryClass, 0, $pos + 1) . $class; + } + } + + $properties['modelClass'] = $class; + $properties['primaryModel'] = $this; + $properties['link'] = $link; + $properties['multiple'] = false; + return new ActiveRelation($properties); + } + + /** + * Declares a `has-many` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the columns in the table associated with the `$class` model, while the values of the + * array refer to the corresponding columns in the table associated with this AR class. + * @param array $properties additional property values that should be used to + * initialize the newly created relation object. + * @return ActiveRelation the relation object. + */ + public function hasMany($class, $link, $properties = array()) + { + $relation = $this->hasOne($class, $link, $properties); + $relation->multiple = true; + return $relation; + } + + /** + * Initializes the internal storage for the relation. + * This method is internally used by [[ActiveQuery]] when populating relation data. + * @param ActiveRelation $relation the relation object + */ + public function initRelation($relation) + { + $this->_related[$relation->name] = $relation->hasMany ? array() : null; + } + + /** + * @param ActiveRelation $relation + * @param ActiveRecord $record + */ + public function addRelatedRecord($relation, $record) + { + if ($relation->hasMany) { + if ($relation->indexBy !== null) { + $this->_related[$relation->name][$record->{$relation->indexBy}] = $record; + } else { + $this->_related[$relation->name][] = $record; + } + } else { + $this->_related[$relation->name] = $record; + } + } + + /** + * Returns the related record(s). + * This method will return the related record(s) of the current record. + * If the relation is HAS_ONE or BELONGS_TO, it will return a single object + * or null if the object does not exist. + * If the relation is HAS_MANY or MANY_MANY, it will return an array of objects + * or an empty array. + * @param ActiveRelation|string $relation the relation object or the name of the relation + * @param array|\Closure $params additional parameters that customize the query conditions as specified in the relation declaration. + * @return mixed the related object(s). + * @throws Exception if the relation is not specified in [[relations()]]. + */ + public function findByRelation($relation, $params = array()) + { + if (is_string($relation)) { + $md = $this->getMetaData(); + if (!isset($md->relations[$relation])) { + throw new Exception(get_class($this) . ' has no relation named "' . $relation . '".'); + } + $relation = $md->relations[$relation]; + } + $relation = clone $relation; + if ($params instanceof \Closure) { + $params($relation); + } else { + foreach ($params as $name => $value) { + $relation->$name = $value; + } + } + + $finder = new ActiveFinder($this->getDbConnection()); + return $finder->findWithRecord($this, $relation); + } + + /** + * Returns the list of all attribute names of the model. + * The default implementation will return all column names of the table associated with this AR class. + * @return array list of attribute names. + */ + public function attributes() + { + return array_keys($this->getTableSchema()->columns); + } + + /** + * Returns the named attribute value. + * If this is a new record and the attribute is not set before, + * the default column value will be returned. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * You may also use $this->AttributeName to obtain the attribute value. + * @param string $name the attribute name + * @return mixed the attribute value. Null if the attribute is not set or does not exist. + * @see hasAttribute + */ + public function getAttribute($name) + { + return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + + /** + * Sets the named attribute value. + * You may also use $this->AttributeName to set the attribute value. + * @param string $name the attribute name + * @param mixed $value the attribute value. + * @see hasAttribute + */ + public function setAttribute($name, $value) + { + $this->_attributes[$name] = $value; + } + + /** + * Returns the attribute values that have been modified since they are loaded or saved most recently. + * @param string[]|null $names the names of the attributes whose values may be returned if they are + * changed recently. If null, [[attributes()]] will be used. + * @return array the changed attribute values (name-value pairs) + */ + public function getChangedAttributes($names = null) + { + if ($names === null) { + $names = $this->attributes(); + } + $names = array_flip($names); + $attributes = array(); + if ($this->_oldAttributes === null) { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name])) { + $attributes[$name] = $value; + } + } + } else { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { + $attributes[$name] = $value; + } + } + } + return $attributes; + } + + /** + * Saves the current record. + * + * The record is inserted as a row into the database table if its {@link isNewRecord} + * property is true (usually the case when the record is created using the 'new' + * operator). Otherwise, it will be used to update the corresponding row in the table + * (usually the case if the record is obtained using one of those 'find' methods.) + * + * Validation will be performed before saving the record. If the validation fails, + * the record will not be saved. You can call {@link getErrors()} to retrieve the + * validation errors. + * + * If the record is saved via insertion, its {@link isNewRecord} property will be + * set false, and its {@link scenario} property will be set to be 'update'. + * And if its primary key is auto-incremental and is not set before insertion, + * the primary key will be populated with the automatically generated key value. + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the saving succeeds + */ + public function save($runValidation = true, $attributes = null) + { + if (!$runValidation || $this->validate($attributes)) { + return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes); + } + return false; + } + + /** + * Inserts a row into the table based on this active record attributes. + * If the table's primary key is auto-incremental and is null before insertion, + * it will be populated with the actual value after insertion. + * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. + * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false, + * and its {@link scenario} property will be set to be 'update'. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the attributes are valid and the record is inserted successfully. + * @throws Exception if the record is not new + */ + public function insert($attributes = null) + { + if ($this->beforeInsert()) { + $query = new Query; + $values = $this->getChangedAttributes($attributes); + $db = $this->getDbConnection(); + $command = $query->insert($this->tableName(), $values)->createCommand($db); + if ($command->execute()) { + $table = $this->getTableSchema(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $name) { + if (!isset($this->_attributes[$name])) { + $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName); + break; + } + } + } + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $value; + } + $this->afterInsert(); + return true; + } + } + return false; + } + + /** + * Updates the row represented by this active record. + * All loaded attributes will be saved to the database. + * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the update is successful + * @throws Exception if the record is new + */ + public function update($attributes = null) + { + if ($this->getIsNewRecord()) { + throw new Exception('The active record cannot be updated because it is new.'); + } + if ($this->beforeUpdate()) { + $values = $this->getChangedAttributes($attributes); + if ($values !== array()) { + $this->updateAll($values, $this->getOldPrimaryKey(true)); + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + $this->afterUpdate(); + } + return true; + } else { + return false; + } + } + + /** + * Saves one or several counter columns for the current AR object. + * Note that this method differs from [[updateAllCounters()]] in that it only + * saves counters for the current AR object. + * + * An example usage is as follows: + * + * ~~~ + * $post = Post::find($id)->one(); + * $post->updateCounters(array('view_count' => 1)); + * ~~~ + * + * Use negative values if you want to decrease the counters. + * @param array $counters the counters to be updated (attribute name => increment value) + * @return boolean whether the saving is successful + * @throws Exception if the record is new or any database error + * @see updateAllCounters() + */ + public function updateCounters($counters) + { + if ($this->getIsNewRecord()) { + throw new Exception('The active record cannot be updated because it is new.'); + } + $this->updateAllCounters($counters, $this->getOldPrimaryKey(true)); + foreach ($counters as $name => $value) { + $this->_attributes[$name] += $value; + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + return true; + } + + /** + * Deletes the row corresponding to this active record. + * @return boolean whether the deletion is successful. + * @throws Exception if the record is new or any database error + */ + public function delete() + { + if ($this->getIsNewRecord()) { + throw new Exception('The active record cannot be deleted because it is new.'); + } + if ($this->beforeDelete()) { + $result = $this->deleteAll($this->getPrimaryKey(true)) > 0; + $this->_oldAttributes = null; + $this->afterDelete(); + return $result; + } else { + return false; + } + } + + /** + * Returns if the current record is new. + * @return boolean whether the record is new and should be inserted when calling {@link save}. + * This property is automatically set in constructor and {@link populateRecord}. + * Defaults to false, but it will be set to true if the instance is created using + * the new operator. + */ + public function getIsNewRecord() + { + return $this->_oldAttributes === null; + } + + /** + * Sets if the record is new. + * @param boolean $value whether the record is new and should be inserted when calling {@link save}. + * @see getIsNewRecord + */ + public function setIsNewRecord($value) + { + $this->_oldAttributes = $value ? null : $this->_attributes; + } + + /** + * This method is invoked before saving a record (after validation, if any). + * The default implementation raises the `beforeSave` event. + * You may override this method to do any preparation work for record saving. + * Use {@link isNewRecord} to determine whether the saving is + * for inserting or updating record. + * Make sure you call the parent implementation so that the event is raised properly. + * @return boolean whether the saving should be executed. Defaults to true. + */ + public function beforeInsert() + { + $event = new ModelEvent($this); + $this->trigger('beforeInsert', $event); + return $event->isValid; + } + + /** + * This method is invoked after saving a record successfully. + * The default implementation raises the `afterSave` event. + * You may override this method to do postprocessing after record saving. + * Make sure you call the parent implementation so that the event is raised properly. + */ + public function afterInsert() + { + $this->trigger('afterInsert', new Event($this)); + } + + /** + * This method is invoked before saving a record (after validation, if any). + * The default implementation raises the `beforeSave` event. + * You may override this method to do any preparation work for record saving. + * Use {@link isNewRecord} to determine whether the saving is + * for inserting or updating record. + * Make sure you call the parent implementation so that the event is raised properly. + * @return boolean whether the saving should be executed. Defaults to true. + */ + public function beforeUpdate() + { + $event = new ModelEvent($this); + $this->trigger('beforeUpdate', $event); + return $event->isValid; + } + + /** + * This method is invoked after saving a record successfully. + * The default implementation raises the `afterSave` event. + * You may override this method to do postprocessing after record saving. + * Make sure you call the parent implementation so that the event is raised properly. + */ + public function afterUpdate() + { + $this->trigger('afterUpdate', new Event($this)); + } + + /** + * This method is invoked before deleting a record. + * The default implementation raises the `beforeDelete` event. + * You may override this method to do any preparation work for record deletion. + * Make sure you call the parent implementation so that the event is raised properly. + * @return boolean whether the record should be deleted. Defaults to true. + */ + public function beforeDelete() + { + $event = new ModelEvent($this); + $this->trigger('beforeDelete', $event); + return $event->isValid; + } + + /** + * This method is invoked after deleting a record. + * The default implementation raises the `afterDelete` event. + * You may override this method to do postprocessing after the record is deleted. + * Make sure you call the parent implementation so that the event is raised properly. + */ + public function afterDelete() + { + $this->trigger('afterDelete', new Event($this)); + } + + /** + * Repopulates this active record with the latest data. + * @param array $attributes + * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record. + */ + public function refresh($attributes = null) + { + if ($this->getIsNewRecord()) { + return false; + } + $record = $this->find()->where($this->getPrimaryKey(true))->one(); + if ($record === null) { + return false; + } + if ($attributes === null) { + foreach ($this->attributes() as $name) { + $this->_attributes[$name] = $record->_attributes[$name]; + } + $this->_oldAttributes = $this->_attributes; + } else { + foreach ($attributes as $name) { + $this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name]; + } + } + return true; + } + + /** + * 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 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) + { + return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); + } + + /** + * Returns the primary key value. + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column name as key and column value as value. + * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite. + * If primary key is not defined, null will be returned. + */ + public function getPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (count($keys) === 1 && !$asArray) { + return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; + } else { + $values = array(); + foreach ($keys as $name) { + $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + return $values; + } + } + + /** + * Returns the old primary key value. + * This refers to the primary key value that is populated into the record + * after executing a find method (e.g. find(), findAll()). + * The value remains unchanged even if the primary key attribute is manually assigned with a different value. + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column name as key and column value as value. + * If this is false (default), a scalar value will be returned for non-composite primary key. + * @return string|array the old primary key value. An array (column name=>column value) is returned if the primary key is composite. + * If primary key is not defined, null will be returned. + */ + public function getOldPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (count($keys) === 1 && !$asArray) { + return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; + } else { + $values = array(); + foreach ($keys as $name) { + $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + } + return $values; + } + } + + /** + * Creates an active record with the given attributes. + * @param array $row attribute values (name => value) + * @return ActiveRecord the newly created active record. + */ + public static function create($row) + { + $record = static::instantiate($row); + $columns = static::getTableSchema()->columns; + foreach ($row as $name => $value) { + if (isset($columns[$name])) { + $record->_attributes[$name] = $value; + } else { + $record->$name = $value; + } + } + $record->_oldAttributes = $record->_attributes; + return $record; + } + + /** + * Creates an active record instance. + * This method is called by [[createRecord()]]. + * You may override this method if the instance being created + * depends the attributes that are to be populated to the record. + * For example, by creating a record based on the value of a column, + * you may implement the so-called single-table inheritance mapping. + * @param array $row list of attribute values for the active records. + * @return ActiveRecord the active record + */ + public static function instantiate($row) + { + return new static; + } + + /** + * Returns whether there is an element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean + */ + public function offsetExists($offset) + { + return $this->__isset($offset); + } + + +} diff --git a/framework/db/ActiveRecordBehavior.php b/framework/db/ActiveRecordBehavior.php new file mode 100644 index 0000000..b4b7b5e --- /dev/null +++ b/framework/db/ActiveRecordBehavior.php @@ -0,0 +1,102 @@ + + * @since 2.0 + */ +class ActiveRecordBehavior extends ModelBehavior +{ + /** + * Declares events and the corresponding event handler methods. + * If you override this method, make sure you merge the parent result to the return value. + * @return array events (array keys) and the corresponding event handler methods (array values). + * @see \yii\base\Behavior::events() + */ + public function events() + { + return array_merge(parent::events(), array( + 'beforeInsert' => 'beforeInsert', + 'afterInsert' => 'afterInsert', + 'beforeUpdate' => 'beforeUpdate', + 'afterUpdate' => 'afterUpdate', + 'beforeDelete' => 'beforeDelete', + 'afterDelete' => 'afterDelete', + )); + } + + /** + * Responds to the owner's `beforeInsert` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter + * to be false to quit the ActiveRecord inserting process. + * @param \yii\base\ModelEvent $event event parameter + */ + public function beforeInsert($event) + { + } + + /** + * Responds to the owner's `afterInsert` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * @param \yii\base\ModelEvent $event event parameter + */ + public function afterInsert($event) + { + } + + /** + * Responds to the owner's `beforeUpdate` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter + * to be false to quit the ActiveRecord updating process. + * @param \yii\base\ModelEvent $event event parameter + */ + public function beforeUpdate($event) + { + } + + /** + * Responds to the owner's `afterUpdate` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * @param \yii\base\ModelEvent $event event parameter + */ + public function afterUpdate($event) + { + } + + /** + * Responds to the owner's `beforeDelete` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter + * to be false to quit the ActiveRecord deleting process. + * @param \yii\base\ModelEvent $event event parameter + */ + public function beforeDelete($event) + { + } + + /** + * Responds to the owner's `afterDelete` event. + * Overrides this method if you want to handle the corresponding event of the owner. + * @param \yii\base\ModelEvent $event event parameter + */ + public function afterDelete($event) + { + } +} diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php new file mode 100644 index 0000000..013df4b --- /dev/null +++ b/framework/db/ActiveRelation.php @@ -0,0 +1,269 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\db; + +use yii\db\Connection; +use yii\db\Command; +use yii\db\QueryBuilder; + +/** + * It is used in three scenarios: + * - eager loading: User::find()->with('posts')->all(); + * - lazy loading: $user->posts; + * - lazy loading with query options: $user->posts()->where('status=1')->get(); + * + * @author Qiang Xue + * @since 2.0 + */ +class ActiveRelation extends ActiveQuery +{ + /** + * @var boolean whether this relation should populate all query results into AR instances. + * If false, only the first row of the results will be taken. + */ + public $multiple; + /** + * @var ActiveRecord the primary model that this relation is associated with. + * This is used only in lazy loading with dynamic query options. + */ + public $primaryModel; + /** + * @var array the columns of the primary and foreign tables that establish the relation. + * The array keys must be columns of the table for this relation, and the array values + * must be the corresponding columns from the primary table. + * Do not prefix or quote the column names as they will be done automatically by Yii. + */ + public $link; + /** + * @var array|ActiveRelation + */ + public $via; + + /** + * @param string $relationName + * @param array|\Closure $options + * @return ActiveRelation + */ + public function via($relationName, $options = null) + { + /** @var $relation ActiveRelation */ + $relation = $this->primaryModel->$relationName(); + $relation->primaryModel = null; + $this->via = array($relationName, $relation); + if (is_array($options)) { + foreach ($options as $name => $value) { + $this->$name = $value; + } + } elseif ($options instanceof \Closure) { + $options($relation); + } + return $this; + } + + /** + * @param string $tableName + * @param array $link + * @param array|\Closure $options + * @return ActiveRelation + */ + public function viaTable($tableName, $link, $options = null) + { + $relation = new ActiveRelation(array( + 'modelClass' => get_class($this->primaryModel), + 'from' => array($tableName), + 'link' => $link, + 'multiple' => true, + 'asArray' => true, + )); + $this->via = $relation; + if (is_array($options)) { + foreach ($options as $name => $value) { + $this->$name = $value; + } + } elseif ($options instanceof \Closure) { + $options($relation); + } + return $this; + } + + /** + * Creates a DB command that can be used to execute this query. + * @return Command the created DB command instance. + */ + public function createCommand() + { + if ($this->primaryModel !== null) { + // lazy loading + if ($this->via instanceof self) { + // via pivot table + $viaModels = $this->via->findPivotRows(array($this->primaryModel)); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + $relationName = $this->via[0]; + $viaModels = $this->primaryModel->$relationName; + if ($viaModels === null) { + $viaModels = array(); + } elseif (!is_array($viaModels)) { + $viaModels = array($viaModels); + } + $this->filterByModels($viaModels); + } else { + $this->filterByModels(array($this->primaryModel)); + } + } + return parent::createCommand(); + } + + public function findWith($name, &$primaryModels) + { + if (!is_array($this->link)) { + throw new \yii\base\Exception('invalid link'); + } + + if ($this->via instanceof self) { + // via pivot table + /** @var $viaQuery ActiveRelation */ + $viaQuery = $this->via; + $viaModels = $viaQuery->findPivotRows($primaryModels); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + /** @var $viaQuery ActiveRelation */ + list($viaName, $viaQuery) = $this->via; + $viaModels = $viaQuery->findWith($viaName, $primaryModels); + $this->filterByModels($viaModels); + } else { + $this->filterByModels($primaryModels); + } + + if (count($primaryModels) === 1 && !$this->multiple) { + $model = $this->one(); + foreach ($primaryModels as $i => $primaryModel) { + $primaryModels[$i][$name] = $model; + } + return array($model); + } else { + $models = $this->all(); + if (isset($viaModels, $viaQuery)) { + $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link); + } else { + $buckets = $this->buildBuckets($models, $this->link); + } + + $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); + foreach ($primaryModels as $i => $primaryModel) { + $key = $this->getModelKey($primaryModel, $link); + if (isset($buckets[$key])) { + $primaryModels[$i][$name] = $buckets[$key]; + } else { + $primaryModels[$i][$name] = $this->multiple ? array() : null; + } + } + return $models; + } + } + + protected function buildBuckets($models, $link, $viaModels = null, $viaLink = null) + { + $buckets = array(); + $linkKeys = array_keys($link); + foreach ($models as $i => $model) { + $key = $this->getModelKey($model, $linkKeys); + if ($this->indexBy !== null) { + $buckets[$key][$i] = $model; + } else { + $buckets[$key][] = $model; + } + } + + if ($viaModels !== null) { + $viaBuckets = array(); + $viaLinkKeys = array_keys($viaLink); + $linkValues = array_values($link); + foreach ($viaModels as $viaModel) { + $key1 = $this->getModelKey($viaModel, $viaLinkKeys); + $key2 = $this->getModelKey($viaModel, $linkValues); + if (isset($buckets[$key2])) { + foreach ($buckets[$key2] as $i => $bucket) { + if ($this->indexBy !== null) { + $viaBuckets[$key1][$i] = $bucket; + } else { + $viaBuckets[$key1][] = $bucket; + } + } + } + } + $buckets = $viaBuckets; + } + + if (!$this->multiple) { + foreach ($buckets as $i => $bucket) { + $buckets[$i] = reset($bucket); + } + } + return $buckets; + } + + protected function getModelKey($model, $attributes) + { + if (count($attributes) > 1) { + $key = array(); + foreach ($attributes as $attribute) { + $key[] = $model[$attribute]; + } + return serialize($key); + } else { + $attribute = reset($attributes); + return $model[$attribute]; + } + } + + protected function filterByModels($models) + { + $attributes = array_keys($this->link); + $values = array(); + if (count($attributes) ===1) { + // single key + $attribute = reset($this->link); + foreach ($models as $model) { + $values[] = $model[$attribute]; + } + } else { + // composite keys + foreach ($models as $model) { + $v = array(); + foreach ($this->link as $attribute => $link) { + $v[$attribute] = $model[$link]; + } + $values[] = $v; + } + } + $this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR))); + } + + /** + * @param ActiveRecord[] $primaryModels + * @return array + */ + protected function findPivotRows($primaryModels) + { + if (empty($primaryModels)) { + return array(); + } + $this->filterByModels($primaryModels); + /** @var $primaryModel ActiveRecord */ + $primaryModel = reset($primaryModels); + $db = $primaryModel->getDbConnection(); + $sql = $db->getQueryBuilder()->build($this); + return $db->createCommand($sql, $this->params)->queryAll(); + } +} diff --git a/framework/db/BaseQuery.php b/framework/db/BaseQuery.php new file mode 100644 index 0000000..58b8f57 --- /dev/null +++ b/framework/db/BaseQuery.php @@ -0,0 +1,555 @@ + + * @since 2.0 + */ +class BaseQuery extends \yii\base\Component +{ + /** + * @var string|array the columns being selected. This refers to the SELECT clause in a SQL + * statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`). + * If not set, if means all columns. + * @see select() + */ + public $select; + /** + * @var string additional option that should be appended to the 'SELECT' keyword. For example, + * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. + */ + public $selectOption; + /** + * @var boolean whether to select distinct rows of data only. If this is set true, + * the SELECT clause would be changed to SELECT DISTINCT. + */ + public $distinct; + /** + * @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement. + * It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`). + * @see from() + */ + public $from; + /** + * @var string|array query condition. This refers to the WHERE clause in a SQL statement. + * For example, `age > 31 AND team = 1`. + * @see where() + */ + public $where; + /** + * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. + */ + public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. If not set or + * less than 0, it means starting from the beginning. + */ + public $offset; + /** + * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement. + * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`). + */ + public $orderBy; + /** + * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement. + * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`). + */ + public $groupBy; + /** + * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement. + * It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g. + * `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`). + * @see join() + */ + public $join; + /** + * @var string|array the condition to be applied in the GROUP BY clause. + * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition. + */ + public $having; + /** + * @var string|BaseQuery[] the UNION clause(s) in a SQL statement. This can be either a string + * representing a single UNION clause or an array representing multiple UNION clauses. + * Each union clause can be a string or a `BaseQuery` object which refers to the SQL statement. + */ + public $union; + /** + * @var array list of query parameter values indexed by parameter placeholders. + * For example, `array(':name'=>'Dan', ':age'=>31)`. + */ + public $params; + + /** + * Sets the SELECT part of the query. + * @param string|array $columns the columns to be selected. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id"). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, + * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. + * @return BaseQuery the query object itself + */ + public function select($columns, $option = null) + { + $this->select = $columns; + $this->selectOption = $option; + return $this; + } + + /** + * Sets the value indicating whether to SELECT DISTINCT or not. + * @param bool $value whether to SELECT DISTINCT or not. + * @return BaseQuery the query object itself + */ + public function distinct($value = true) + { + $this->distinct = $value; + return $this; + } + + /** + * Sets the FROM part of the query. + * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`) + * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names. + * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`). + * The method will automatically quote the table names unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @return BaseQuery the query object itself + */ + public function from($tables) + { + $this->from = $tables; + return $this; + } + + /** + * Sets the WHERE part of the query. + * + * The method requires a $condition parameter, and optionally a $params parameter + * specifying the values to be bound to the query. + * + * The $condition parameter should be either a string (e.g. 'id=1') or an array. + * If the latter, it must be in one of the following two formats: + * + * - hash format: `array('column1' => value1, 'column2' => value2, ...)` + * - operator format: `array(operator, operand1, operand2, ...)` + * + * A condition in hash format represents the following SQL expression in general: + * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, + * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used + * in the generated expression. Below are some examples: + * + * - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`. + * - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`. + * - `array('status'=>null) generates `status IS NULL`. + * + * A condition in operator format generates the SQL expression according to the specified operator, which + * can be one of the followings: + * + * - `and`: the operands should be concatenated together using `AND`. For example, + * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array, + * it will be converted into a string using the rules described here. For example, + * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`. + * The method will NOT do any quoting or escaping. + * + * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. + * + * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the + * starting and ending values of the range that the column is in. + * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`. + * + * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` + * in the generated condition. + * + * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing + * the range of the values that the column or DB expression should be in. For example, + * `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`. + * The method will properly quote the column name and escape values in the range. + * + * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. + * + * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing + * the values that the column or DB expression should be like. + * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`. + * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated + * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate + * `name LIKE '%test%' AND name LIKE '%sample%'`. + * The method will properly quote the column name and escape values in the range. + * + * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` + * predicates when operand 2 is an array. + * + * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` + * in the generated condition. + * + * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate + * the `NOT LIKE` predicates. + * + * @param string|array $condition the conditions that should be put in the WHERE part. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see andWhere() + * @see orWhere() + */ + public function where($condition, $params = array()) + { + $this->where = $condition; + $this->addParams($params); + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see where() + * @see orWhere() + */ + public function andWhere($condition, $params = array()) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = array('and', $this->where, $condition); + } + $this->addParams($params); + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see where() + * @see andWhere() + */ + public function orWhere($condition, $params = array()) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = array('or', $this->where, $condition); + } + $this->addParams($params); + return $this; + } + + /** + * Appends a JOIN part to the query. + * The first parameter specifies what type of join it is. + * @param string $type the type of join, such as INNER JOIN, LEFT JOIN. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + */ + public function join($type, $table, $on = '', $params = array()) + { + $this->join[] = array($type, $table, $on); + return $this->addParams($params); + } + + /** + * Appends an INNER JOIN part to the query. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + */ + public function innerJoin($table, $on = '', $params = array()) + { + $this->join[] = array('INNER JOIN', $table, $on); + return $this->addParams($params); + } + + /** + * Appends a LEFT OUTER JOIN part to the query. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query + * @return BaseQuery the query object itself + */ + public function leftJoin($table, $on = '', $params = array()) + { + $this->join[] = array('LEFT JOIN', $table, $on); + return $this->addParams($params); + } + + /** + * Appends a RIGHT OUTER JOIN part to the query. + * @param string $table the table to be joined. + * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query + * @return BaseQuery the query object itself + */ + public function rightJoin($table, $on = '', $params = array()) + { + $this->join[] = array('RIGHT JOIN', $table, $on); + return $this->addParams($params); + } + + /** + * Sets the GROUP BY part of the query. + * @param string|array $columns the columns to be grouped by. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return BaseQuery the query object itself + * @see addGroup() + */ + public function groupBy($columns) + { + $this->groupBy = $columns; + return $this; + } + + /** + * Adds additional group-by columns to the existing ones. + * @param string|array $columns additional columns to be grouped by. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return BaseQuery the query object itself + * @see group() + */ + public function addGroup($columns) + { + if (empty($this->groupBy)) { + $this->groupBy = $columns; + } else { + if (!is_array($this->groupBy)) { + $this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY); + } + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + $this->groupBy = array_merge($this->groupBy, $columns); + } + return $this; + } + + /** + * Sets the HAVING part of the query. + * @param string|array $condition the conditions to be put after HAVING. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see andHaving() + * @see orHaving() + */ + public function having($condition, $params = array()) + { + $this->having = $condition; + $this->addParams($params); + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see having() + * @see orHaving() + */ + public function andHaving($condition, $params = array()) + { + if ($this->having === null) { + $this->having = $condition; + } else { + $this->having = array('and', $this->having, $condition); + } + $this->addParams($params); + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return BaseQuery the query object itself + * @see having() + * @see andHaving() + */ + public function orHaving($condition, $params = array()) + { + if ($this->having === null) { + $this->having = $condition; + } else { + $this->having = array('or', $this->having, $condition); + } + $this->addParams($params); + return $this; + } + + /** + * Sets the ORDER BY part of the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return BaseQuery the query object itself + * @see addOrder() + */ + public function orderBy($columns) + { + $this->orderBy = $columns; + return $this; + } + + /** + * Adds additional ORDER BY columns to the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return BaseQuery the query object itself + * @see order() + */ + public function addOrderBy($columns) + { + if (empty($this->orderBy)) { + $this->orderBy = $columns; + } else { + if (!is_array($this->orderBy)) { + $this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY); + } + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + $this->orderBy = array_merge($this->orderBy, $columns); + } + return $this; + } + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit + * @return BaseQuery the query object itself + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset + * @return BaseQuery the query object itself + */ + public function offset($offset) + { + $this->offset = $offset; + return $this; + } + + /** + * Appends a SQL statement using UNION operator. + * @param string|BaseQuery $sql the SQL statement to be appended using UNION + * @return BaseQuery the query object itself + */ + public function union($sql) + { + $this->union[] = $sql; + return $this; + } + + /** + * Sets the parameters to be bound to the query. + * @param array $params list of query parameter values indexed by parameter placeholders. + * For example, `array(':name'=>'Dan', ':age'=>31)`. + * @return BaseQuery the query object itself + * @see addParams() + */ + public function params($params) + { + $this->params = $params; + return $this; + } + + /** + * Adds additional parameters to be bound to the query. + * @param array $params list of query parameter values indexed by parameter placeholders. + * For example, `array(':name'=>'Dan', ':age'=>31)`. + * @return BaseQuery the query object itself + * @see params() + */ + public function addParams($params) + { + if ($params !== array()) { + if ($this->params === null) { + $this->params = $params; + } else { + foreach ($params as $name => $value) { + if (is_integer($name)) { + $this->params[] = $value; + } else { + $this->params[$name] = $value; + } + } + } + } + return $this; + } + + /** + * Merges this query with another one. + * If a property of `$query` is not null, it will be used to overwrite + * the corresponding property of `$this`. + * @param BaseQuery $query the new query to be merged with this query. + * @return BaseQuery the query object itself + */ + public function mergeWith(BaseQuery $query) + { + $properties = array( + 'select', 'selectOption', 'distinct', 'from', + 'where', 'limit', 'offset', 'orderBy', 'groupBy', + 'join', 'having', 'union', 'params', + ); + // todo: incorrect, do we need it? should we provide a configure() method instead? + foreach ($properties as $name => $value) { + if ($value !== null) { + $this->$name = $value; + } + } + return $this; + } +} diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php new file mode 100644 index 0000000..2e0c75c --- /dev/null +++ b/framework/db/ColumnSchema.php @@ -0,0 +1,127 @@ + + * @since 2.0 + */ +class ColumnSchema extends \yii\base\Component +{ + /** + * @var string name of this column (without quotes). + */ + public $name; + /** + * @var string the quoted name of this column. + */ + public $quotedName; + /** + * @var boolean whether this column can be null. + */ + public $allowNull; + /** + * @var string logical type of this column. Possible logic types include: + * string, text, boolean, smallint, integer, bigint, float, decimal, datetime, + * timestamp, time, date, binary, and money. + */ + public $type; + /** + * @var string the PHP type of this column. Possible PHP types include: + * string, boolean, integer, double. + */ + public $phpType; + /** + * @var string the DB type of this column. Possible DB types vary according to the type of DBMS. + */ + public $dbType; + /** + * @var mixed default value of this column + */ + public $defaultValue; + /** + * @var array enumerable values. This is set only if the column is declared to be an enumerable type. + */ + public $enumValues; + /** + * @var integer display size of the column. + */ + public $size; + /** + * @var integer precision of the column data, if it is numeric. + */ + public $precision; + /** + * @var integer scale of the column data, if it is numeric. + */ + public $scale; + /** + * @var boolean whether this column is a primary key + */ + public $isPrimaryKey; + /** + * @var boolean whether this column is auto-incremental + */ + public $autoIncrement = false; + /** + * @var boolean whether this column is unsigned. This is only meaningful + * when [[type]] is `smallint`, `integer` or `bigint`. + */ + public $unsigned; + + /** + * Extracts the PHP type from DB type. + */ + public function resolvePhpType() + { + static $typeMap = array( // logical type => php type + 'smallint' => 'integer', + 'integer' => 'integer', + 'bigint' => 'integer', + 'boolean' => 'boolean', + 'float' => 'double', + ); + if (isset($typeMap[$this->type])) { + if ($this->type === 'bigint') { + $this->phpType = PHP_INT_SIZE == 8 && !$this->unsigned ? 'integer' : 'string'; + } elseif ($this->type === 'integer') { + $this->phpType = PHP_INT_SIZE == 4 && $this->unsigned ? 'string' : 'integer'; + } else { + $this->phpType = $typeMap[$this->type]; + } + } else { + $this->phpType = 'string'; + } + } + + /** + * Converts the input value according to [[phpType]]. + * If the value is null or an [[Expression]], it will not be converted. + * @param mixed $value input value + * @return mixed converted value + */ + public function typecast($value) + { + if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { + return $value; + } + switch ($this->phpType) { + case 'string': + return (string)$value; + case 'integer': + return (integer)$value; + case 'boolean': + return (boolean)$value; + } + return $value; + } +} diff --git a/framework/db/Command.php b/framework/db/Command.php new file mode 100644 index 0000000..c9b133f --- /dev/null +++ b/framework/db/Command.php @@ -0,0 +1,424 @@ +db->createCommand('SELECT * FROM tbl_user')->queryAll(); + * ~~~ + * + * Command supports SQL statement preparation and parameter binding. + * Call [[bindValue()]] to bind a value to a SQL parameter; + * Call [[bindParam()]] to bind a PHP variable to a SQL parameter. + * When binding a parameter, the SQL statement is automatically prepared. + * You may also call [[prepare()]] explicitly to prepare a SQL statement. + * + * @property string $sql the SQL statement to be executed + * + * @author Qiang Xue + * @since 2.0 + */ +class Command extends \yii\base\Component +{ + /** + * @var Connection the DB connection that this command is associated with + */ + public $connection; + /** + * @var \PDOStatement the PDOStatement object that this command contains + */ + public $pdoStatement; + /** + * @var mixed the default fetch mode for this command. + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + */ + public $fetchMode = \PDO::FETCH_ASSOC; + /** + * @var string the SQL statement that this command represents + */ + private $_sql; + /** + * @var array the parameter log information (name=>value) + */ + private $_params = array(); + + /** + * Constructor. + * @param Connection $connection the database connection + * @param string $sql the SQL statement to be executed + * @param array $params the parameters to be bound to the SQL statement + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($connection, $sql = null, $params = array(), $config = array()) + { + $this->connection = $connection; + $this->_sql = $sql; + $this->bindValues($params); + parent::__construct($config); + } + + /** + * Returns the SQL statement for this command. + * @return string the SQL statement to be executed + */ + public function getSql() + { + return $this->_sql; + } + + /** + * Specifies the SQL statement to be executed. + * Any previous execution will be terminated or cancelled. + * @param string $value the SQL statement to be set. + * @return Command this command instance + */ + public function setSql($value) + { + $this->_sql = $value; + $this->_params = array(); + $this->cancel(); + return $this; + } + + /** + * 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. + * @throws Exception if there is any DB error + */ + public function prepare() + { + if ($this->pdoStatement == null) { + $sql = $this->connection->expandTablePrefix($this->getSql()); + try { + $this->pdoStatement = $this->connection->pdo->prepare($sql); + } catch (\Exception $e) { + \Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__); + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + throw new Exception($e->getMessage(), (int)$e->getCode(), $errorInfo); + } + } + } + + /** + * Cancels the execution of the SQL statement. + * This method mainly sets [[pdoStatement]] to be null. + */ + public function cancel() + { + $this->pdoStatement = null; + } + + /** + * Binds a parameter to the SQL statement to be executed. + * @param string|integer $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 + * @return Command the current command being executed + * @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->pdoStatement->bindParam($name, $value, $this->connection->getPdoType(gettype($value))); + } elseif ($length === null) { + $this->pdoStatement->bindParam($name, $value, $dataType); + } elseif ($driverOptions === null) { + $this->pdoStatement->bindParam($name, $value, $dataType, $length); + } else { + $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); + } + $this->_params[$name] =& $value; + return $this; + } + + /** + * Binds a value to a parameter. + * @param string|integer $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 Command the current command being executed + * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php + */ + public function bindValue($name, $value, $dataType = null) + { + $this->prepare(); + if ($dataType === null) { + $this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value))); + } else { + $this->pdoStatement->bindValue($name, $value, $dataType); + } + $this->_params[$name] = $value; + return $this; + } + + /** + * Binds a list of values to the corresponding parameters. + * This is similar to [[bindValue()]] except that it binds multiple values at a time. + * 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, + * e.g. `array(':name'=>'John', ':age'=>25)`. By default, the PDO type of each value is determined + * by its PHP type. You may explicitly specify the PDO type by using an array: `array(value, type)`, + * e.g. `array(':name'=>'John', ':profile'=>array($profile, \PDO::PARAM_LOB))`. + * @return Command the current command being executed + */ + public function bindValues($values) + { + if (!empty($values)) { + $this->prepare(); + foreach ($values as $name => $value) { + if (is_array($value)) { + $type = $value[1]; + $value = $value[0]; + } else { + $type = $this->connection->getPdoType(gettype($value)); + } + $this->pdoStatement->bindValue($name, $value, $type); + $this->_params[$name] = $value; + } + } + return $this; + } + + /** + * Executes the SQL statement. + * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. + * No result set will be returned. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @return integer number of rows affected by the execution. + * @throws Exception execution failed + */ + public function execute($params = array()) + { + $sql = $this->connection->expandTablePrefix($this->getSql()); + $this->_params = array_merge($this->_params, $params); + if ($this->_params === array()) { + $paramLog = ''; + } else { + $paramLog = "\nParameters: " . var_export($this->_params, true); + } + + \Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__); + + try { + if ($this->connection->enableProfiling) { + \Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__); + } + + $this->prepare(); + if ($params === array()) { + $this->pdoStatement->execute(); + } else { + $this->pdoStatement->execute($params); + } + $n = $this->pdoStatement->rowCount(); + + if ($this->connection->enableProfiling) { + \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); + } + return $n; + } catch (\Exception $e) { + if ($this->connection->enableProfiling) { + \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); + } + $message = $e->getMessage(); + \Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__); + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + throw new Exception($message, (int)$e->getCode(), $errorInfo); + } + } + + /** + * Executes the SQL statement and returns query result. + * This method is for executing a SQL query that returns result set, such as `SELECT`. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @return DataReader the reader object for fetching the query result + * @throws Exception execution failed + */ + public function query($params = array()) + { + return $this->queryInternal('', $params); + } + + /** + * Executes the SQL statement and returns ALL rows at once. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return array all rows of the query result. Each array element is an array representing a row of data. + * An empty array is returned if the query results in nothing. + * @throws Exception execution failed + */ + public function queryAll($params = array(), $fetchMode = null) + { + return $this->queryInternal('fetchAll', $params, $fetchMode); + } + + /** + * Executes the SQL statement and returns the first row of the result. + * This method is best used when only the first row of result is needed for a query. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. + * @throws Exception execution failed + */ + public function queryRow($params = array(), $fetchMode = null) + { + return $this->queryInternal('fetch', $params, $fetchMode); + } + + /** + * Executes the SQL statement and returns the value of the first column in the first row of data. + * This method is best used when only a single value is needed for a query. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @return string|boolean the value of the first column in the first row of the query result. + * False is returned if there is no value. + * @throws Exception execution failed + */ + public function queryScalar($params = array()) + { + $result = $this->queryInternal('fetchColumn', $params, 0); + 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 method is best used when only the first column of result (i.e. the first element in each row) + * is needed for a query. + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @return array the first column of the query result. Empty array is returned if the query results in nothing. + * @throws Exception execution failed + */ + public function queryColumn($params = array()) + { + return $this->queryInternal('fetchAll', $params, \PDO::FETCH_COLUMN); + } + + /** + * Performs the actual DB query of a SQL statement. + * @param string $method method of PDOStatement to be called + * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative + * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] + * or [[bindValue()]] will be ignored. + * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return mixed the method execution result + */ + private function queryInternal($method, $params, $fetchMode = null) + { + $db = $this->connection; + $sql = $db->expandTablePrefix($this->getSql()); + $this->_params = array_merge($this->_params, $params); + if ($this->_params === array()) { + $paramLog = ''; + } else { + $paramLog = "\nParameters: " . var_export($this->_params, true); + } + + \Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__); +echo $sql."\n\n"; + if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') { + $cache = \Yii::$application->getComponent($db->queryCacheID); + } + + if (isset($cache)) { + $db->queryCachingCount--; + $cacheKey = __CLASS__ . "/{$db->dsn}/{$db->username}/$sql/$paramLog"; + if (($result = $cache->get($cacheKey)) !== false) { + \Yii::trace('Query result found in cache', __CLASS__); + return $result; + } + } + + try { + if ($db->enableProfiling) { + \Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__); + } + + $this->prepare(); + if ($params === array()) { + $this->pdoStatement->execute(); + } else { + $this->pdoStatement->execute($params); + } + + if ($method === '') { + $result = new DataReader($this); + } else { + if ($fetchMode === null) { + $fetchMode = $this->fetchMode; + } + $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode); + $this->pdoStatement->closeCursor(); + } + + if ($db->enableProfiling) { + \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); + } + + if (isset($cache)) { + $cache->set($cacheKey, $result, $db->queryCachingDuration, $db->queryCachingDependency); + \Yii::trace('Saved query result in cache', __CLASS__); + } + + return $result; + } catch (\Exception $e) { + if ($db->enableProfiling) { + \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); + } + $message = $e->getMessage(); + \Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__); + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + throw new Exception($message, (int)$e->getCode(), $errorInfo); + } + } +} diff --git a/framework/db/Connection.php b/framework/db/Connection.php new file mode 100644 index 0000000..93cf8b7 --- /dev/null +++ b/framework/db/Connection.php @@ -0,0 +1,623 @@ + $dsn, + * 'username' => $username, + * 'password' => $password, + * )); + * $connection->active = true; // same as: $connection->open(); + * ~~~ + * + * After the DB connection is established, one can execute SQL statements like the following: + * + * ~~~ + * $command = $connection->createCommand('SELECT * FROM tbl_post'); + * $posts = $command->queryAll(); + * $command = $connection->createCommand('UPDATE tbl_post SET status=1'); + * $command->execute(); + * ~~~ + * + * One can also do prepared SQL execution and bind parameters to the prepared SQL. + * When the parameters are coming from user input, you should use this approach + * to prevent SQL injection attacks. The following is an example: + * + * ~~~ + * $command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=:id'); + * $command->bindValue(':id', $_GET['id']); + * $post = $command->query(); + * ~~~ + * + * For more information about how to perform various DB queries, please refer to [[Command]]. + * + * If the underlying DBMS supports transactions, you can perform transactional SQL queries + * like the following: + * + * ~~~ + * $transaction = $connection->beginTransaction(); + * try { + * $connection->createCommand($sql1)->execute(); + * $connection->createCommand($sql2)->execute(); + * // ... executing other SQL statements ... + * $transaction->commit(); + * } catch(Exception $e) { + * $transaction->rollBack(); + * } + * ~~~ + * + * Connection is often used as an [[\yii\base\ApplicationComponent|application component]] and configured in the application + * configuration like the following: + * + * ~~~ + * array( + * 'components' => array( + * 'db' => array( + * 'class' => '\yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ), + * ), + * ) + * ~~~ + * + * @property boolean $active Whether the DB connection is established. + * @property Transaction $currentTransaction The currently active transaction. Null if no active transaction. + * @property Driver $driver The database driver for the current connection. + * @property QueryBuilder $queryBuilder The query builder. + * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the sequence object. + * @property string $driverName Name of the DB driver currently being used. + * @property string $clientVersion The version information of the DB driver. + * @property array $stats The statistical results of SQL executions. + * + * @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. + * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on + * the format of the DSN string. + * @see charset + */ + 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 array PDO attributes (name=>value) that should be set when calling [[open]] + * to establish a DB connection. Please refer to the + * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for + * details about available attributes. + */ + public $attributes; + /** + * @var \PDO the PHP PDO instance associated with this DB connection. + * This property is mainly managed by [[open]] and [[close]] methods. + * When a DB connection is active, this property will represent a PDO instance; + * otherwise, it will be null. + */ + public $pdo; + /** + * @var integer number of seconds that table metadata can remain valid in cache. + * Defaults to -1, meaning schema caching is disabled. + * Use 0 to indicate that the cached data will never expire. + * + * Note that in order to enable schema caching, a valid cache component as specified + * by [[schemaCacheID]] must be enabled. + * @see schemaCachingExclude + * @see schemaCacheID + */ + public $schemaCachingDuration = -1; + /** + * @var array list of tables whose metadata should NOT be cached. Defaults to empty array. + * The table names may contain schema prefix, if any. Do not quote the table names. + * @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'. + * @see schemaCachingDuration + */ + public $schemaCacheID = 'cache'; + /** + * @var integer number of seconds that query results can remain valid in cache. + * Defaults to -1, meaning query caching is disabled. + * Use 0 to indicate that the cached data will never expire. + * + * Note that in order to enable query caching, a valid cache component as specified + * by [[queryCacheID]] must be enabled. + * + * The method [[cache()]] is provided as a convenient way of setting this property + * and [[queryCachingDependency]] on the fly. + * + * @see cache + * @see queryCachingDependency + * @see queryCacheID + */ + public $queryCachingDuration = -1; + /** + * @var \yii\caching\Dependency the dependency that will be used when saving query results into cache. + * Defaults to null, meaning no dependency. + * @see queryCachingDuration + */ + public $queryCachingDependency; + /** + * @var integer the number of SQL statements that need to be cached when they are executed next. + * Defaults to 0, meaning the query result of the next SQL statement will NOT 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. + * @see queryCachingDuration + */ + public $queryCachingCount = 0; + /** + * @var string the ID of the cache application component that is used for query caching. + * Defaults to 'cache'. + * @see queryCachingDuration + */ + public $queryCacheID = 'cache'; + /** + * @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. + * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed. + */ + public $emulatePrepare; + /** + * @var boolean whether to enable profiling for 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. + * @see getStats + */ + public $enableProfiling = false; + /** + * @var string the default prefix for table names. Defaults to null, meaning not using table prefix. + * By setting this property, any token like '{{TableName}}' in [[Command::sql]] will + * be replaced with 'prefixTableName', where 'prefix' refers to this property value. + * For example, '{{post}}' becomes 'tbl_post', if 'tbl_' is set as the table prefix. + * + * Note that if you set this property to be an empty string, then '{{post}}' will be replaced + * with 'post'. + */ + public $tablePrefix; + /** + * @var array a list of SQL statements that should be executed right after the DB connection is established. + */ + public $initSQLs; + /** + * @var array mapping between PDO driver names and [[Driver]] classes. + * The keys of the array are PDO driver names while the values the corresponding + * driver class name or configuration. Please refer to [[\Yii::createObject]] for + * details on how to specify a configuration. + * + * This property is mainly used by [[getDriver()]] when fetching the database schema information. + * You normally do not need to set this property unless you want to use your own + * [[Driver]] class to support DBMS that is not supported by Yii. + */ + public $driverMap = array( + 'pgsql' => 'yii\db\pgsql\Driver', // PostgreSQL + 'mysqli' => 'yii\db\mysql\Driver', // MySQL + 'mysql' => 'yii\db\mysql\Driver', // MySQL + 'sqlite' => 'yii\db\sqlite\Driver', // sqlite 3 + 'sqlite2' => 'yii\db\sqlite\Driver', // sqlite 2 + 'mssql' => 'yi\db\dao\mssql\Driver', // Mssql driver on windows hosts + 'dblib' => 'yii\db\mssql\Driver', // dblib drivers on linux (and maybe others os) hosts + 'sqlsrv' => 'yii\db\mssql\Driver', // Mssql + 'oci' => 'yii\db\oci\Driver', // Oracle driver + ); + /** + * @var Transaction the currently active transaction + */ + private $_transaction; + /** + * @var Driver the database driver + */ + private $_driver; + + /** + * Closes the connection when this component is being serialized. + * @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(); + } + + /** + * Returns a value indicating whether the DB connection is established. + * @return boolean whether the DB connection is established + */ + public function getActive() + { + return $this->pdo !== null; + } + + /** + * Opens or closes the DB connection. + * @param boolean $value whether to open or close the DB connection + * @throws Exception if there is any error when establishing the connection + */ + public function setActive($value) + { + $value ? $this->open() : $this->close(); + } + + /** + * Sets the parameters about query caching. + * This method is provided as a shortcut to setting three properties that are related + * with query caching: [[queryCachingDuration]], [[queryCachingDependency]] and + * [[queryCachingCount]]. + * @param integer $duration the number of seconds that query results may remain valid in cache. + * See [[queryCachingDuration]] for more details. + * @param \yii\caching\Dependency $dependency the dependency for the cached query result. + * See [[queryCachingDependency]] for more details. + * @param integer $queryCount the number of SQL queries that need to be cached after calling this method. + * See [[queryCachingCount]] for more details. + * @return Connection the connection instance itself. + */ + public function cache($duration = 300, $dependency = null, $queryCount = 1) + { + $this->queryCachingDuration = $duration; + $this->queryCachingDependency = $dependency; + $this->queryCachingCount = $queryCount; + return $this; + } + + /** + * Establishes a DB connection. + * It does nothing if a DB connection has already been established. + * @throws Exception if connection fails + */ + public function open() + { + if ($this->pdo === null) { + if (empty($this->dsn)) { + throw new Exception('Connection.dsn cannot be empty.'); + } + try { + \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); + $this->pdo = $this->createPdoInstance(); + $this->initConnection($this->pdo); + } + catch (\PDOException $e) { + \Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__); + $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; + throw new Exception($message, (int)$e->getCode(), $e->errorInfo); + } + } + } + + /** + * Closes the currently active DB connection. + * It does nothing if the connection is already closed. + */ + public function close() + { + if ($this->pdo !== null) { + \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); + $this->pdo = null; + $this->_driver = null; + $this->_transaction = null; + } + } + + /** + * Creates the PDO instance. + * This method is called by [[open]] to establish a DB connection. + * The default implementation will create a PHP PDO instance. + * You may override this method if the default PDO needs to be adapted for certain DBMS. + * @return \PDO the pdo instance + */ + protected function createPdoInstance() + { + $pdoClass = '\PDO'; + if (($pos = strpos($this->dsn, ':')) !== false) { + $driver = strtolower(substr($this->dsn, 0, $pos)); + if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { + $pdoClass = 'mssql\PDO'; + } + } + return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); + } + + /** + * Initializes the DB connection. + * This method is invoked right after the DB connection is established. + * The default implementation sets the database [[charset]] and executes SQLs specified + * in [[initSQLs]]. + */ + protected function initConnection() + { + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + if ($this->emulatePrepare !== null && constant('\PDO::ATTR_EMULATE_PREPARES')) { + $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); + } + if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli'))) { + $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); + } + if (!empty($this->initSQLs)) { + foreach ($this->initSQLs as $sql) { + $this->pdo->exec($sql); + } + } + } + + /** + * Creates a command for execution. + * @param string $sql the SQL statement to be executed + * @param array $params the parameters to be bound to the SQL statement + * @return Command the DB command + */ + public function createCommand($sql = null, $params = array()) + { + $this->open(); + return new Command($this, $sql, $params); + } + + /** + * Returns the currently active transaction. + * @return Transaction the currently active transaction. Null if no active transaction. + */ + public function getCurrentTransaction() + { + if ($this->_transaction !== null && $this->_transaction->active) { + return $this->_transaction; + } else { + return null; + } + } + + /** + * Starts a transaction. + * @return Transaction the transaction initiated + */ + public function beginTransaction() + { + \Yii::trace('Starting transaction', __CLASS__); + $this->open(); + $this->pdo->beginTransaction(); + return $this->_transaction = new Transaction($this); + } + + /** + * Returns the metadata information for the underlying database. + * @return Driver the metadata information for the underlying database. + */ + public function getDriver() + { + if ($this->_driver !== null) { + return $this->_driver; + } else { + $driver = $this->getDriverName(); + if (isset($this->driverMap[$driver])) { + return $this->_driver = \Yii::createObject($this->driverMap[$driver], $this); + } else { + throw new Exception("Connection does not support reading metadata for '$driver' database."); + } + } + } + + /** + * Returns the query builder for the current DB connection. + * @return QueryBuilder the query builder for the current DB connection. + */ + public function getQueryBuilder() + { + return $this->getDriver()->getQueryBuilder(); + } + + /** + * Obtains the metadata for the named table. + * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return TableSchema table metadata. Null if the named table does not exist. + */ + public function getTableSchema($name, $refresh = false) + { + return $this->getDriver()->getTableSchema($name, $refresh); + } + + /** + * 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->open(); + return $this->pdo->lastInsertId($sequenceName); + } + + /** + * Quotes a string value for use in a query. + * Note that if the parameter is not a string, it will be returned without change. + * @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_string($str)) { + return $str; + } + + $this->open(); + 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 + * @param boolean $simple if this is true, then the method will assume $name is a table name without schema prefix. + * @return string the properly quoted table name + */ + public function quoteTableName($name, $simple = false) + { + return $simple ? $this->getDriver()->quoteSimpleTableName($name) : $this->getDriver()->quoteTableName($name); + } + + /** + * Quotes a column name for use in a query. + * If the column name contains table prefix, the prefix will also be properly quoted. + * @param string $name column name + * @param boolean $simple if this is true, then the method will assume $name is a column name without table prefix. + * @return string the properly quoted column name + */ + public function quoteColumnName($name, $simple = false) + { + return $simple ? $this->getDriver()->quoteSimpleColumnName($name) : $this->getDriver()->quoteColumnName($name); + } + + /** + * Prefixes table names in a SQL statement with [[tablePrefix]]. + * By calling this method, tokens like '{{TableName}}' in the given SQL statement will + * be replaced with 'prefixTableName', where 'prefix' refers to [[tablePrefix]]. + * Note that if [[tablePrefix]] is null, this method will do nothing. + * @param string $sql the SQL statement whose table names need to be prefixed with [[tablePrefix]]. + * @return string the expanded SQL statement + * @see tablePrefix + */ + public function expandTablePrefix($sql) + { + if ($this->tablePrefix !== null && strpos($sql, '{{') !== false) { + return preg_replace('/{{(.*?)}}/', $this->tablePrefix . '\1', $sql); + } else { + return $sql; + } + } + + /** + * Determines the PDO type for the give PHP data type. + * @param string $type The PHP type (obtained by `gettype()` call). + * @return integer the corresponding PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($type) + { + static $typeMap = array( + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'NULL' => \PDO::PARAM_NULL, + ); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** + * Returns the name of the DB driver for the current [[dsn]]. + * @return string name of the DB driver + */ + public function getDriverName() + { + if (($pos = strpos($this->dsn, ':')) !== false) { + return strtolower(substr($this->dsn, 0, $pos)); + } else { + return strtolower($this->getAttribute(\PDO::ATTR_DRIVER_NAME)); + } + } + + /** + * 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->open(); + 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) + { + $this->open(); + $this->pdo->setAttribute($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, [[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. + * @see \yii\logging\Logger::getProfiling() + */ + public function getStats() + { + $logger = \Yii::getLogger(); + $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); + $count = count($timings); + $time = 0; + foreach ($timings as $timing) { + $time += $timing[1]; + } + return array($count, $time); + } +} diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php new file mode 100644 index 0000000..26168f9 --- /dev/null +++ b/framework/db/DataReader.php @@ -0,0 +1,256 @@ + + * @since 2.0 + */ +class DataReader extends \yii\base\Object implements \Iterator, \Countable +{ + /** + * @var \PDOStatement the PDOStatement associated with the command + */ + private $_statement; + private $_closed = false; + private $_row; + private $_index = -1; + + /** + * Constructor. + * @param Command $command the command generating the query result + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct(Command $command, $config = array()) + { + $this->_statement = $command->pdoStatement; + $this->_statement->setFetchMode(\PDO::FETCH_ASSOC); + parent::__construct($config); + } + + /** + * 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 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 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 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 attempts 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 Exception if this method is invoked twice + */ + public function rewind() + { + if ($this->_index < 0) { + $this->_row = $this->_statement->fetch(); + $this->_index = 0; + } else { + throw new Exception('DataReader 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/Driver.php b/framework/db/Driver.php new file mode 100644 index 0000000..a04e867 --- /dev/null +++ b/framework/db/Driver.php @@ -0,0 +1,283 @@ + + * @since 2.0 + */ +abstract class Driver extends \yii\base\Object +{ + /** + * The followings are the supported abstract column data types. + */ + const TYPE_PK = 'pk'; + const TYPE_STRING = 'string'; + const TYPE_TEXT = 'text'; + const TYPE_SMALLINT = 'smallint'; + const TYPE_INTEGER = 'integer'; + const TYPE_BIGINT = 'bigint'; + const TYPE_FLOAT = 'float'; + const TYPE_DECIMAL = 'decimal'; + const TYPE_DATETIME = 'datetime'; + const TYPE_TIMESTAMP = 'timestamp'; + const TYPE_TIME = 'time'; + const TYPE_DATE = 'date'; + const TYPE_BINARY = 'binary'; + const TYPE_BOOLEAN = 'boolean'; + const TYPE_MONEY = 'money'; + + /** + * @var Connection the database connection + */ + public $connection; + /** + * @var array list of ALL table names in the database + */ + private $_tableNames = array(); + /** + * @var array list of loaded table metadata (table name => TableSchema) + */ + private $_tables = array(); + /** + * @var QueryBuilder the query builder for this database + */ + private $_builder; + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema DBMS-dependent table metadata, null if the table does not exist. + */ + abstract protected function loadTableSchema($name); + + /** + * Constructor. + * @param Connection $connection database connection. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($connection, $config = array()) + { + $this->connection = $connection; + parent::__construct($config); + } + + /** + * Obtains the metadata for the named table. + * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return TableSchema table metadata. Null if the named table does not exist. + */ + public function getTableSchema($name, $refresh = false) + { + if (isset($this->_tables[$name]) && !$refresh) { + return $this->_tables[$name]; + } + + $db = $this->connection; + + $realName = $db->expandTablePrefix($name); + + // temporarily disable query caching + if ($db->queryCachingDuration >= 0) { + $qcDuration = $db->queryCachingDuration; + $db->queryCachingDuration = -1; + } + + if (!in_array($name, $db->schemaCachingExclude, true) && $db->schemaCachingDuration >= 0 && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null) { + $key = $this->getCacheKey($name); + if ($refresh || ($table = $cache->get($key)) === false) { + $table = $this->loadTableSchema($realName); + if ($table !== null) { + $cache->set($key, $table, $db->schemaCachingDuration); + } + } + $this->_tables[$name] = $table; + } else { + $this->_tables[$name] = $table = $this->loadTableSchema($realName); + } + + if (isset($qcDuration)) { // re-enable query caching + $db->queryCachingDuration = $qcDuration; + } + + return $table; + } + + /** + * Returns the cache key for the specified table name. + * @param string $name the table name + * @return string the cache key + */ + public function getCacheKey($name) + { + return __CLASS__ . "/{$this->connection->dsn}/{$this->connection->username}/{$name}"; + } + + /** + * Returns the metadata for all tables in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array the metadata for all tables in the database. + * Each array element is an instance of [[TableSchema]] (or its child class). + */ + public function getTableSchemas($schema = '') + { + $tables = array(); + foreach ($this->getTableNames($schema) as $name) { + if (($table = $this->getTableSchema($name)) !== null) { + $tables[] = $table; + } + } + return $tables; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @param boolean $refresh whether to fetch the latest available table names. If this is false, + * table names fetched previously (if available) will be returned. + * @return array all table names in the database. + */ + public function getTableNames($schema = '', $refresh = false) + { + if (!isset($this->_tableNames[$schema]) || $refresh) { + $this->_tableNames[$schema] = $this->findTableNames($schema); + } + return $this->_tableNames[$schema]; + } + + /** + * @return QueryBuilder the query builder for this connection. + */ + public function getQueryBuilder() + { + if ($this->_builder === null) { + $this->_builder = $this->createQueryBuilder(); + } + return $this->_builder; + } + + /** + * Refreshes the schema. + * This method cleans up the cached table schema and names + * so that they can be recreated to reflect the database schema change. + * @param string $tableName the name of the table that needs to be refreshed. + * If null, all currently loaded tables will be refreshed. + */ + public function refresh($tableName = null) + { + $db = $this->connection; + if ($db->schemaCachingDuration >= 0 && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null) { + if ($tableName === null) { + foreach ($this->_tables as $name => $table) { + $cache->delete($this->getCacheKey($name)); + } + $this->_tables = array(); + } else { + $cache->delete($this->getCacheKey($tableName)); + unset($this->_tables[$tableName]); + } + } + } + + /** + * 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 + * @see quoteSimpleTableName + */ + public function quoteTableName($name) + { + if (strpos($name, '.') === false) { + return $this->quoteSimpleTableName($name); + } + $parts = explode('.', $name); + foreach ($parts as $i => $part) { + $parts[$i] = $this->quoteSimpleTableName($part); + } + return implode('.', $parts); + + } + + /** + * Quotes a simple table name for use in a query. + * A simple table name does not schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "'") !== false ? $name : "'" . $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 + * @see quoteSimpleColumnName + */ + public function quoteColumnName($name) + { + if (($pos = strrpos($name, '.')) !== false) { + $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.'; + $name = substr($name, $pos + 1); + } else { + $prefix = ''; + } + return $prefix . $this->quoteSimpleColumnName($name); + } + + /** + * Quotes a simple column name for use in a query. + * A simple column name does not contain prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; + } + + /** + * Creates a query builder for the database. + * This method may be overridden by child classes to create a DBMS-specific query builder. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->connection); + } + + /** + * Returns all table names in the database. + * This method should be overridden by child classes in order to support this feature + * because the default implementation simply throws an exception. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @return array all table names in the database. + */ + protected function findTableNames($schema = '') + { + throw new Exception(get_class($this) . 'does not support fetching all table names.'); + } +} diff --git a/framework/db/Expression.php b/framework/db/Expression.php new file mode 100644 index 0000000..3ff6374 --- /dev/null +++ b/framework/db/Expression.php @@ -0,0 +1,62 @@ + + * @since 2.0 + */ +class Expression extends \yii\base\Object +{ + /** + * @var string the DB expression + */ + public $expression; + /** + * @var array list of parameters that should be bound for this expression. + * The keys are placeholders appearing in [[expression]] and the values + * are the corresponding parameter values. + */ + public $params = array(); + + /** + * Constructor. + * @param string $expression the DB expression + * @param array $params parameters + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($expression, $params = array(), $config = array()) + { + $this->expression = $expression; + $this->params = $params; + parent::__construct($config); + } + + /** + * String magic method + * @return string the DB expression + */ + public function __toString() + { + return $this->expression; + } +} \ No newline at end of file diff --git a/framework/db/Query.php b/framework/db/Query.php new file mode 100644 index 0000000..2ca072a --- /dev/null +++ b/framework/db/Query.php @@ -0,0 +1,310 @@ +select('id, name') + * ->from('tbl_user') + * ->limit(10); + * // get the actual SQL statement + * echo $query->getSql(); + * // or execute the query + * $users = $query->createCommand()->queryAll(); + * ~~~ + * + * By calling [[getSql()]], we can obtain the actual SQL statement from a Query object. + * And by calling [[createCommand()]], we can get a [[Command]] instance which can be further + * used to perform/execute the DB query against a database. + * + * @property string $sql the SQL statement represented by this query object. + * + * @author Qiang Xue + * @since 2.0 + */ +class Query extends BaseQuery +{ + /** + * @var array the operation that this query represents. This refers to the method call as well as + * the corresponding parameters for constructing a non-select SQL statement (e.g. INSERT, CREATE TABLE). + * This property is mainly maintained by methods such as [[insert()]], [[update()]], [[createTable()]]. + * If this property is not set, it means this query represents a SELECT statement. + */ + public $operation; + /** + * @var string the SQL statement that this query represents. This is directly set by user. + */ + public $sql; + + /** + * Generates and returns the SQL statement according to this query. + * Note that after calling this method, [[params]] may be modified with additional + * parameters generated by the query builder. + * @param Connection $connection the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return string the generated SQL statement + */ + public function getSql($connection = null) + { + if ($this->sql !== null) { + return $this->sql; + } + if ($connection === null) { + $connection = \Yii::$application->db; + } + $qb = $connection->getQueryBuilder(); + if ($this->operation !== null) { + $params = $this->operation; + $method = array_shift($params); + $qb->query = $this; + return call_user_func_array(array($qb, $method), $params); + } else { + /** @var $qb QueryBuilder */ + return $qb->build($this); + } + } + + /** + * Creates a DB command that can be used to execute this query. + * @param Connection $connection the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return Command the created DB command instance. + */ + public function createCommand($connection = null) + { + if ($connection === null) { + $connection = \Yii::$application->db; + } + $sql = $this->getSql($connection); + return $connection->createCommand($sql, $this->params); + } + + /** + * 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 Query the query object itself + */ + public function insert($table, $columns) + { + $this->operation = array(__FUNCTION__, $table, $columns); + return $this; + } + + /** + * Creates and executes an UPDATE SQL statement. + * The method will properly escape the column names and bind the values to be updated. + * @param string $table the table to be updated. + * @param array $columns the column data (name=>value) to be updated. + * @param string|array $condition the conditions that will be put in the WHERE part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return Query the query object itself + */ + public function update($table, $columns, $condition = '', $params = array()) + { + $this->operation = array(__FUNCTION__, $table, $columns, $condition, $params); + return $this; + } + + /** + * Creates and executes a DELETE SQL statement. + * @param string $table the table where the data will be deleted from. + * @param string|array $condition the conditions that will be put in the WHERE part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return Query the query object itself + */ + public function delete($table, $condition = '', $params = array()) + { + $this->operation = array(__FUNCTION__, $table, $condition, $params); + return $this; + } + + /** + * Builds and executes a SQL statement for creating a new DB table. + * + * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), + * where name stands for a column name which will be properly quoted by the method, and definition + * stands for the column type which can contain an abstract DB type. + * The method [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the abstract column types to physical ones. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly + * inserted into the generated SQL. + * + * @param string $table the name of the table to be created. The name will be properly quoted by the method. + * @param array $columns the columns (name=>definition) in the new table. + * @param string $options additional SQL fragment that will be appended to the generated SQL. + * @return Query the query object itself + */ + public function createTable($table, $columns, $options = null) + { + $this->operation = array(__FUNCTION__, $table, $columns, $options); + return $this; + } + + /** + * Builds and executes a SQL statement for renaming a DB table. + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function renameTable($table, $newName) + { + $this->operation = array(__FUNCTION__, $table, $newName); + return $this; + } + + /** + * Builds and executes a SQL statement for dropping a DB table. + * @param string $table the table to be dropped. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function dropTable($table) + { + $this->operation = array(__FUNCTION__, $table); + return $this; + } + + /** + * Builds and executes a SQL statement for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function truncateTable($table) + { + $this->operation = array(__FUNCTION__, $table); + return $this; + } + + /** + * Builds and executes a SQL statement for adding a new DB column. + * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. + * @param string $column the name of the new column. The name will be properly quoted by the method. + * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the give column type to the physical one. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * @return Query the query object itself + */ + public function addColumn($table, $column, $type) + { + $this->operation = array(__FUNCTION__, $table, $column, $type); + return $this; + } + + /** + * Builds and executes a SQL statement for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function dropColumn($table, $column) + { + $this->operation = array(__FUNCTION__, $table, $column); + return $this; + } + + /** + * Builds and executes a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function renameColumn($table, $oldName, $newName) + { + $this->operation = array(__FUNCTION__, $table, $oldName, $newName); + return $this; + } + + /** + * Builds and executes a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the give column type to the physical one. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * @return Query the query object itself + */ + public function alterColumn($table, $column, $type) + { + $this->operation = array(__FUNCTION__, $table, $column, $type); + return $this; + } + + /** + * Builds a SQL statement for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string $refTable the table that the foreign key references to. + * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @return Query the query object itself + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + $this->operation = array(__FUNCTION__, $name, $table, $columns, $refTable, $refColumns, $delete, $update); + return $this; + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function dropForeignKey($name, $table) + { + $this->operation = array(__FUNCTION__, $name, $table); + return $this; + } + + /** + * Builds and executes a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas. The column names will be properly quoted by the method. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return Query the query object itself + */ + public function createIndex($name, $table, $columns, $unique = false) + { + $this->operation = array(__FUNCTION__, $name, $table, $columns, $unique); + return $this; + } + + /** + * Builds and executes a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return Query the query object itself + */ + public function dropIndex($name, $table) + { + $this->operation = array(__FUNCTION__, $name, $table); + return $this; + } +} diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php new file mode 100644 index 0000000..3cfc874 --- /dev/null +++ b/framework/db/QueryBuilder.php @@ -0,0 +1,947 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\base\Object +{ + /** + * @var Connection the database connection. + */ + public $connection; + /** + * @var string the separator between different fragments of a SQL statement. + * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. + */ + public $separator = " "; + /** + * @var boolean whether to automatically quote table and column names when generating SQL statements. + */ + public $autoQuote = true; + /** + * @var array the abstract column types mapped to physical column types. + * This is mainly used to support creating/modifying tables using DB-independent data type specifications. + * Child classes should override this property to declare supported type mappings. + */ + public $typeMap = array(); + /** + * @var Query the Query object that is currently being processed by the query builder to generate a SQL statement. + */ + public $query; + + /** + * Constructor. + * @param Connection $connection the database connection. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($connection, $config = array()) + { + $this->connection = $connection; + parent::__construct($config); + } + + /** + * Generates a SELECT SQL statement from a [[BaseQuery]] object. + * @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated + * @return string the generated SQL statement + */ + public function build($query) + { + $clauses = array( + $this->buildSelect($query->select, $query->distinct, $query->selectOption), + $this->buildFrom($query->from), + $this->buildJoin($query->join), + $this->buildWhere($query->where), + $this->buildGroup($query->groupBy), + $this->buildHaving($query->having), + $this->buildUnion($query->union), + $this->buildOrder($query->orderBy), + $this->buildLimit($query->limit, $query->offset), + ); + return implode($this->separator, array_filter($clauses)); + } + + /** + * Creates and executes an INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * For example, + * + * ~~~ + * $sql = $queryBuilder->insert('tbl_user', array( + * 'name' => 'Sam', + * 'age' => 30, + * )); + * ~~~ + * + * @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 string the INSERT SQL + */ + public function insert($table, $columns) + { + $names = array(); + $placeholders = array(); + $count = 0; + $params = array(); + foreach ($columns as $name => $value) { + $names[] = $this->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $placeholders[] = ':p' . $count; + $params[':p' . $count] = $value; + $count++; + } + } + if ($this->query instanceof BaseQuery) { + $this->query->addParams($params); + } + + return 'INSERT INTO ' . $this->quoteTableName($table) + . ' (' . implode(', ', $names) . ') VALUES (' + . implode(', ', $placeholders) . ')'; + } + + /** + * Creates and executes an UPDATE SQL statement. + * The method will properly escape the column names and bind the values to be updated. + * For example, + * + * ~~~ + * $params = array(); + * $sql = $queryBuilder->update('tbl_user', array( + * 'status' => 1, + * ), 'age > 30', $params); + * ~~~ + * + * @param string $table the table to be updated. + * @param array $columns the column data (name=>value) to be updated. + * @param mixed $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the parameters to be bound to the query. + * @return string the UPDATE SQL + */ + public function update($table, $columns, $condition = '', $params = array()) + { + $lines = array(); + $count = 0; + foreach ($columns as $name => $value) { + if ($value instanceof Expression) { + $lines[] = $this->quoteColumnName($name, true) . '=' . $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $lines[] = $this->quoteColumnName($name, true) . '=:p' . $count; + $params[':p' . $count] = $value; + $count++; + } + } + if ($this->query instanceof BaseQuery) { + $this->query->addParams($params); + } + $sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines); + if (($where = $this->buildCondition($condition)) !== '') { + $sql .= ' WHERE ' . $where; + } + + return $sql; + } + + /** + * Creates and executes a DELETE SQL statement. + * For example, + * + * ~~~ + * $sql = $queryBuilder->delete('tbl_user', 'status = 0'); + * ~~~ + * + * @param string $table the table where the data will be deleted from. + * @param mixed $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the parameters to be bound to the query. + * @return string the DELETE SQL + */ + public function delete($table, $condition = '', $params = array()) + { + $sql = 'DELETE FROM ' . $this->quoteTableName($table); + if (($where = $this->buildCondition($condition)) !== '') { + $sql .= ' WHERE ' . $where; + } + if ($params !== array() && $this->query instanceof BaseQuery) { + $this->query->addParams($params); + } + return $sql; + } + + /** + * Builds 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 [[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. + * + * For example, + * + * ~~~ + * $sql = $queryBuilder->createTable('tbl_user', array( + * 'id' => 'pk', + * 'name' => 'string', + * 'age' => 'integer', + * )); + * ~~~ + * + * @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 string the SQL statement for creating a new DB table. + */ + public function createTable($table, $columns, $options = null) + { + $cols = array(); + foreach ($columns as $name => $type) { + if (is_string($name)) { + $cols[] = "\t" . $this->quoteColumnName($name) . ' ' . $this->getColumnType($type); + } else { + $cols[] = "\t" . $type; + } + } + $sql = "CREATE TABLE " . $this->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; + return $options === null ? $sql : $sql . ' ' . $options; + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $oldName 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 string the SQL statement for renaming a DB table. + */ + public function renameTable($oldName, $newName) + { + return 'RENAME TABLE ' . $this->quoteTableName($oldName) . ' TO ' . $this->quoteTableName($newName); + } + + /** + * Builds 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 string the SQL statement for dropping a DB table. + */ + public function dropTable($table) + { + return "DROP TABLE " . $this->quoteTableName($table); + } + + /** + * Builds 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 string the SQL statement for truncating a DB table. + */ + public function truncateTable($table) + { + return "TRUNCATE TABLE " . $this->quoteTableName($table); + } + + /** + * Builds 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 [[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 string the SQL statement for adding a new column. + */ + public function addColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->quoteTableName($table) + . ' ADD ' . $this->quoteColumnName($column) . ' ' + . $this->getColumnType($type); + } + + /** + * Builds 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 string the SQL statement for dropping a DB column. + */ + public function dropColumn($table, $column) + { + return "ALTER TABLE " . $this->quoteTableName($table) + . " DROP COLUMN " . $this->quoteColumnName($column, true); + } + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + */ + public function renameColumn($table, $oldName, $newName) + { + return "ALTER TABLE " . $this->quoteTableName($table) + . " RENAME COLUMN " . $this->quoteColumnName($oldName, true) + . " TO " . $this->quoteColumnName($newName, true); + } + + /** + * Builds 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 [[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 string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE ' + . $this->quoteColumnName($column, true) . ' ' + . $this->quoteColumnName($column, true) . ' ' + . $this->getColumnType($type); + } + + /** + * 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|array $columns the name of the column to that the constraint will be added on. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $refTable the table that the foreign key references to. + * @param string|array $refColumns the name of the column that the foreign key references to. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @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 string the SQL statement for adding a foreign key constraint to an existing table. + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + $sql = 'ALTER TABLE ' . $this->quoteTableName($table) + . ' ADD CONSTRAINT ' . $this->quoteColumnName($name) + . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' + . ' REFERENCES ' . $this->quoteTableName($refTable) + . ' (' . $this->buildColumns($refColumns) . ')'; + if ($delete !== null) { + $sql .= ' ON DELETE ' . $delete; + } + if ($update !== null) { + $sql .= ' ON UPDATE ' . $update; + } + return $sql; + } + + /** + * 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 string the SQL statement for dropping a foreign key constraint. + */ + public function dropForeignKey($name, $table) + { + return 'ALTER TABLE ' . $this->quoteTableName($table) + . ' DROP CONSTRAINT ' . $this->quoteColumnName($name); + } + + /** + * Builds 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|array $columns the column(s) that should be included in the index. If there are multiple columns, + * separate them with commas or use an array to represent them. Each column name will be properly quoted + * by the method, unless a parenthesis is found in the name. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return string the SQL statement for creating a new index. + */ + public function createIndex($name, $table, $columns, $unique = false) + { + return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') + . $this->quoteTableName($name) . ' ON ' + . $this->quoteTableName($table) + . ' (' . $this->buildColumns($columns) . ')'; + } + + /** + * Builds 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 string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table); + } + + /** + * Resets the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $table the table schema whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + */ + public function resetSequence($table, $value = null) + { + } + + /** + * Enables or disables integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + */ + public function checkIntegrity($check = true, $schema = '') + { + } + + /** + * Converts an abstract column type into a physical column type. + * The conversion is done using the type map specified in [[typeMap]]. + * The following abstract column types are supported (using MySQL as an example to explain the corresponding + * physical types): + * + * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `string`: string type, will be converted into "varchar(255)" + * - `text`: a long string type, will be converted into "text" + * - `smallint`: a small integer type, will be converted into "smallint(6)" + * - `integer`: integer type, will be converted into "int(11)" + * - `bigint`: a big integer type, will be converted into "bigint(20)" + * - `boolean`: boolean type, will be converted into "tinyint(1)" + * - `float``: float number type, will be converted into "float" + * - `decimal`: decimal number type, will be converted into "decimal" + * - `datetime`: datetime type, will be converted into "datetime" + * - `timestamp`: timestamp type, will be converted into "timestamp" + * - `time`: time type, will be converted into "time" + * - `date`: date type, will be converted into "date" + * - `money`: money type, will be converted into "decimal(19,4)" + * - `binary`: binary data type, will be converted into "blob" + * + * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only + * the first part will be converted, and the rest of the parts will be appended to the converted result. + * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. + * + * If a type cannot be found in [[typeMap]], it will be returned without any change. + * @param string $type abstract column type + * @return string physical column type. + */ + public function getColumnType($type) + { + if (isset($this->typeMap[$type])) { + return $this->typeMap[$type]; + } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { + if (isset($this->typeMap[$matches[0]])) { + return preg_replace('/^\w+/', $this->typeMap[$matches[0]], $type); + } + } + return $type; + } + + /** + * Parses the condition specification and generates the corresponding SQL expression. + * @param string|array $condition the condition specification. Please refer to [[BaseQuery::where()]] + * on how to specify a condition. + * @return string the generated SQL expression + * @throws \yii\db\Exception if the condition is in bad format + */ + public function buildCondition($condition) + { + static $builders = array( + 'AND' => 'buildAndCondition', + 'OR' => 'buildAndCondition', + 'BETWEEN' => 'buildBetweenCondition', + 'NOT BETWEEN' => 'buildBetweenCondition', + 'IN' => 'buildInCondition', + 'NOT IN' => 'buildInCondition', + 'LIKE' => 'buildLikeCondition', + 'NOT LIKE' => 'buildLikeCondition', + 'OR LIKE' => 'buildLikeCondition', + 'OR NOT LIKE' => 'buildLikeCondition', + ); + + if (!is_array($condition)) { + return (string)$condition; + } elseif ($condition === array()) { + return ''; + } + if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... + $operator = strtoupper($condition[0]); + if (isset($builders[$operator])) { + $method = $builders[$operator]; + array_shift($condition); + return $this->$method($operator, $condition); + } else { + throw new Exception('Found unknown operator in query: ' . $operator); + } + } else { // hash format: 'column1'=>'value1', 'column2'=>'value2', ... + return $this->buildHashCondition($condition); + } + } + + private function buildHashCondition($condition) + { + $parts = array(); + foreach ($condition as $column => $value) { + if (is_array($value)) { // IN condition + $parts[] = $this->buildInCondition('in', array($column, $value)); + } else { + if (strpos($column, '(') === false) { + $column = $this->quoteColumnName($column); + } + if ($value === null) { + $parts[] = "$column IS NULL"; + } elseif (is_string($value)) { + $parts[] = "$column=" . $this->connection->quoteValue($value); + } else { + $parts[] = "$column=$value"; + } + } + } + return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; + } + + private function buildAndCondition($operator, $operands) + { + $parts = array(); + foreach ($operands as $operand) { + if (is_array($operand)) { + $operand = $this->buildCondition($operand); + } + if ($operand !== '') { + $parts[] = $operand; + } + } + if ($parts !== array()) { + return '(' . implode(") $operator (", $parts) . ')'; + } else { + return ''; + } + } + + private function buildBetweenCondition($operator, $operands) + { + if (!isset($operands[0], $operands[1], $operands[2])) { + throw new Exception("Operator '$operator' requires three operands."); + } + + list($column, $value1, $value2) = $operands; + + if (strpos($column, '(') === false) { + $column = $this->quoteColumnName($column); + } + $value1 = is_string($value1) ? $this->connection->quoteValue($value1) : (string)$value1; + $value2 = is_string($value2) ? $this->connection->quoteValue($value2) : (string)$value2; + + return "$column $operator $value1 AND $value2"; + } + + private function buildInCondition($operator, $operands) + { + if (!isset($operands[0], $operands[1])) { + throw new Exception("Operator '$operator' requires two operands."); + } + + list($column, $values) = $operands; + + $values = (array)$values; + + if ($values === array() || $column === array()) { + return $operator === 'IN' ? '0=1' : ''; + } + + if (is_array($column)) { + if (count($column) > 1) { + return $this->buildCompositeInCondition($operator, $column, $values); + } else { + $column = reset($column); + foreach ($values as $i => $value) { + if (is_array($value)) { + $value = isset($value[$column]) ? $value[$column] : null; + } + if ($value === null) { + $values[$i] = 'NULL'; + } else { + $values[$i] = is_string($value) ? $this->connection->quoteValue($value) : (string)$value; + } + } + } + } + if (strpos($column, '(') === false) { + $column = $this->quoteColumnName($column); + } + + if (count($values) > 1) { + return "$column $operator (" . implode(', ', $values) . ')'; + } else { + $operator = $operator === 'IN' ? '=' : '<>'; + return "$column$operator{$values[0]}"; + } + } + + protected function buildCompositeInCondition($operator, $columns, $values) + { + foreach ($columns as $i => $column) { + if (strpos($column, '(') === false) { + $columns[$i] = $this->quoteColumnName($column); + } + } + $vss = array(); + foreach ($values as $value) { + $vs = array(); + foreach ($columns as $column) { + if (isset($value[$column])) { + $vs[] = is_string($value[$column]) ? $this->connection->quoteValue($value[$column]) : (string)$value[$column]; + } else { + $vs[] = 'NULL'; + } + } + $vss[] = '(' . implode(', ', $vs) . ')'; + } + return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; + } + + private function buildLikeCondition($operator, $operands) + { + if (!isset($operands[0], $operands[1])) { + throw new Exception("Operator '$operator' requires two operands."); + } + + list($column, $values) = $operands; + + $values = (array)$values; + + 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'; + } + + if (strpos($column, '(') === false) { + $column = $this->quoteColumnName($column); + } + + $parts = array(); + foreach ($values as $value) { + $parts[] = "$column $operator " . $this->connection->quoteValue($value); + } + + return implode($andor, $parts); + } + + /** + * @param string|array $columns + * @param boolean $distinct + * @param string $selectOption + * @return string the SELECT clause built from [[query]]. + */ + public function buildSelect($columns, $distinct = false, $selectOption = null) + { + $select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; + if ($selectOption !== null) { + $select .= ' ' . $selectOption; + } + + if (empty($columns)) { + return $select . ' *'; + } + + if ($this->autoQuote) { + $driver = $this->connection->driver; + if (!is_array($columns)) { + if (strpos($columns, '(') !== false) { + return $select . ' ' . $columns; + } else { + $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+)([\w\-_\.]+)$/', $column, $matches)) { + $columns[$i] = $driver->quoteColumnName($matches[1]) . ' AS ' . $driver->quoteSimpleColumnName($matches[2]); + } else { + $columns[$i] = $driver->quoteColumnName($column); + } + } + } + } + + if (is_array($columns)) { + $columns = implode(', ', $columns); + } + + return $select . ' ' . $columns; + } + + /** + * @param string|array $tables + * @return string the FROM clause built from [[query]]. + */ + public function buildFrom($tables) + { + if (empty($tables)) { + return ''; + } + + if ($this->autoQuote) { + $driver = $this->connection->driver; + if (!is_array($tables)) { + if (strpos($tables, '(') !== false) { + return 'FROM ' . $tables; + } else { + $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+)(.*)$/i', $table, $matches)) { // with alias + $tables[$i] = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]); + } else { + $tables[$i] = $driver->quoteTableName($table); + } + } + } + } + + if (is_array($tables)) { + $tables = implode(', ', $tables); + } + + return 'FROM ' . $tables; + } + + /** + * @param string|array $joins + * @return string the JOIN clause built from [[query]]. + */ + public function buildJoin($joins) + { + if (empty($joins)) { + return ''; + } + if (is_string($joins)) { + return $joins; + } + + foreach ($joins as $i => $join) { + if (is_array($join)) { // 0:join type, 1:table name, 2:on-condition + if (isset($join[0], $join[1])) { + $table = $join[1]; + if ($this->autoQuote && strpos($table, '(') === false) { + $driver = $this->connection->driver; + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias + $table = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]); + } else { + $table = $driver->quoteTableName($table); + } + } + $joins[$i] = $join[0] . ' ' . $table; + if (isset($join[2])) { + $condition = $this->buildCondition($join[2]); + if ($condition !== '') { + $joins[$i] .= ' ON ' . $this->buildCondition($join[2]); + } + } + } else { + throw new Exception('A join clause must be specified as an array of at least two elements.'); + } + } + } + + return implode($this->separator, $joins); + } + + /** + * @param string|array $condition + * @return string the WHERE clause built from [[query]]. + */ + public function buildWhere($condition) + { + $where = $this->buildCondition($condition); + return $where === '' ? '' : 'WHERE ' . $where; + } + + /** + * @param string|array $columns + * @return string the GROUP BY clause + */ + public function buildGroup($columns) + { + if (empty($columns)) { + return ''; + } else { + return 'GROUP BY ' . $this->buildColumns($columns); + } + } + + /** + * @param string|array $condition + * @return string the HAVING clause built from [[query]]. + */ + public function buildHaving($condition) + { + $having = $this->buildCondition($condition); + return $having === '' ? '' : 'HAVING ' . $having; + } + + /** + * @param string|array $columns + * @return string the ORDER BY clause built from [[query]]. + */ + public function buildOrder($columns) + { + if (empty($columns)) { + return ''; + } + if ($this->autoQuote) { + $driver = $this->connection->driver; + if (!is_array($columns)) { + if (strpos($columns, '(') !== false) { + return 'ORDER BY ' . $columns; + } else { + $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] = $driver->quoteColumnName($matches[1]) . ' ' . $matches[2]; + } else { + $columns[$i] = $driver->quoteColumnName($column); + } + } + } + } + if (is_array($columns)) { + $columns = implode(', ', $columns); + } + return 'ORDER BY ' . $columns; + } + + /** + * @param integer $limit + * @param integer $offset + * @return string the LIMIT and OFFSET clauses built from [[query]]. + */ + public function buildLimit($limit, $offset) + { + $sql = ''; + if ($limit !== null && $limit >= 0) { + $sql = 'LIMIT ' . (int)$limit; + } + if ($offset > 0) { + $sql .= ' OFFSET ' . (int)$offset; + } + return ltrim($sql); + } + + /** + * @param string|array $unions + * @return string the UNION clause built from [[query]]. + */ + public function buildUnion($unions) + { + if (empty($unions)) { + return ''; + } + if (!is_array($unions)) { + $unions = array($unions); + } + foreach ($unions as $i => $union) { + if ($union instanceof BaseQuery) { + $unions[$i] = $this->build($union); + } + } + return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)"; + } + + /** + * Processes columns and properly quote them if necessary. + * This method will quote columns if [[autoQuote]] is true. + * It will join all columns into a string with comma as separators. + * @param string|array $columns the columns to be processed + * @return string the processing result + */ + protected function buildColumns($columns) + { + if ($this->autoQuote) { + if (!is_array($columns)) { + if (strpos($columns, '(') !== false) { + return $columns; + } else { + $columns = preg_split('/\s*,\s*/', $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->quoteColumnName($column); + } + } + } + return is_array($columns) ? implode(', ', $columns) : $columns; + } + + /** + * Quotes a table name for use in a query. + * This method will perform name quoting only when [[autoQuote]] is true. + * @param string $name table name + * @param boolean $simple whether the name should be treated as a simple table name without any prefix. + * @return string the properly quoted table name + */ + protected function quoteTableName($name, $simple = false) + { + if ($this->autoQuote) { + return $this->connection->quoteTableName($name, $simple); + } else { + return $name; + } + } + + /** + * Quotes a column name for use in a query. + * This method will perform name quoting only when [[autoQuote]] is true. + * @param string $name column name + * @param boolean $simple whether the name should be treated as a simple column name without any prefix. + * @return string the properly quoted column name + */ + protected function quoteColumnName($name, $simple = false) + { + if ($this->autoQuote) { + return $this->connection->quoteColumnName($name, $simple); + } else { + return $name; + } + } +} diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php new file mode 100644 index 0000000..537c275 --- /dev/null +++ b/framework/db/TableSchema.php @@ -0,0 +1,109 @@ + + * @since 2.0 + */ +class TableSchema extends \yii\base\Object +{ + /** + * @var string name of the catalog (database) that this table belongs to. + * Defaults to null, meaning no catalog (or the current database). + * This property is only meaningful for MSSQL. + */ + public $catalogName; + /** + * @var string name of the schema that this table belongs to. + */ + public $schemaName; + /** + * @var string name of this table. + */ + public $name; + /** + * @var string quoted name of this table. This will include [[schemaName]] if it is not empty. + */ + public $quotedName; + /** + * @var string[] primary keys of this table. + */ + public $primaryKey = array(); + /** + * @var string sequence name for the primary key. Null if no sequence. + */ + public $sequenceName; + /** + * @var array foreign keys of this table. Each array element is of the following structure: + * + * ~~~ + * array( + * 'ForeignTableName', + * 'fk1' => 'pk1', // pk1 is in foreign table + * 'fk2' => 'pk2', // if composite foreign key + * ) + * ~~~ + */ + public $foreignKeys = array(); + /** + * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names. + */ + public $columns = array(); + + /** + * Gets the named column metadata. + * This is a convenient method for retrieving a named column even if it does not exist. + * @param string $name column name + * @return ColumnSchema metadata of the named column. Null if the named column does not exist. + */ + public function getColumn($name) + { + return isset($this->columns[$name]) ? $this->columns[$name] : null; + } + + /** + * Returns the names of all columns in this table. + * @return array list of column names + */ + public function getColumnNames() + { + return array_keys($this->columns); + } + + /** + * Manually specifies the primary key for this table. + * @param string|array $keys the primary key (can be composite) + * @throws \yii\db\Exception if the specified key cannot be found in the table. + */ + public function fixPrimaryKey($keys) + { + if (!is_array($keys)) { + $keys = array($keys); + } + $this->primaryKey = $keys; + foreach ($this->columns as $column) { + $column->isPrimaryKey = false; + } + foreach ($keys as $key) { + if (isset($this->columns[$key])) { + $this->columns[$key]->isPrimaryKey = true; + } else { + throw new Exception("Primary key '$key' cannot be found in table '{$this->name}'."); + } + } + } +} diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php new file mode 100644 index 0000000..bc36785 --- /dev/null +++ b/framework/db/Transaction.php @@ -0,0 +1,91 @@ +beginTransaction(); + * try { + * $connection->createCommand($sql1)->execute(); + * $connection->createCommand($sql2)->execute(); + * //.... other SQL executions + * $transaction->commit(); + * } catch(Exception $e) { + * $transaction->rollBack(); + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Transaction extends \yii\base\Object +{ + /** + * @var boolean whether this transaction is active. Only an active transaction + * can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started. + */ + public $active; + /** + * @var Connection the database connection that this transaction is associated with. + */ + public $connection; + + /** + * Constructor. + * @param Connection $connection the connection associated with this transaction + * @param array $config name-value pairs that will be used to initialize the object properties + * @see Connection::beginTransaction + */ + public function __construct($connection, $config = array()) + { + $this->active = true; + $this->connection = $connection; + parent::__construct($config); + } + + /** + * Commits a transaction. + * @throws Exception if the transaction or the DB connection is not active. + */ + public function commit() + { + if ($this->active && $this->connection->getActive()) { + \Yii::trace('Committing transaction', __CLASS__); + $this->connection->pdo->commit(); + $this->active = false; + } else { + throw new Exception('Failed to commit transaction: transaction was inactive.'); + } + } + + /** + * Rolls back a transaction. + * @throws Exception if the transaction or the DB connection is not active. + */ + public function rollback() + { + if ($this->active && $this->connection->getActive()) { + \Yii::trace('Rolling back transaction', __CLASS__); + $this->connection->pdo->rollBack(); + $this->active = false; + } else { + throw new Exception('Failed to roll back transaction: transaction was inactive.'); + } + } +} diff --git a/framework/db/ar/ActiveQuery.php b/framework/db/ar/ActiveQuery.php deleted file mode 100644 index 11ae08d..0000000 --- a/framework/db/ar/ActiveQuery.php +++ /dev/null @@ -1,273 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2012 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\db\ar; - -use yii\db\dao\Connection; -use yii\db\dao\Command; -use yii\db\dao\QueryBuilder; -use yii\db\dao\BaseQuery; -use yii\base\VectorIterator; -use yii\db\dao\Expression; -use yii\db\Exception; - -class ActiveQuery extends BaseQuery -{ - /** - * @var string the name of the ActiveRecord class. - */ - public $modelClass; - /** - * @var array list of relations that this query should be performed with - */ - public $with; - /** - * @var string the name of the column by which the query result should be indexed. - * This is only used when the query result is returned as an array when calling [[all()]]. - */ - public $indexBy; - /** - * @var boolean whether to return each record as an array. If false (default), an object - * of [[modelClass]] will be created to represent each record. - */ - public $asArray; - /** - * @var array list of scopes that should be applied to this query - */ - public $scopes; - /** - * @var string the SQL statement to be executed for retrieving AR records. - * This is set by [[ActiveRecord::findBySql()]]. - */ - public $sql; - - public function __call($name, $params) - { - if (method_exists($this->modelClass, $name)) { - $this->scopes[$name] = $params; - return $this; - } else { - return parent::__call($name, $params); - } - } - - /** - * Executes query and returns all results as an array. - * @return array the query results. If the query results in nothing, an empty array will be returned. - */ - public function all() - { - $command = $this->createCommand(); - $rows = $command->queryAll(); - if ($rows !== array()) { - $models = $this->createModels($rows); - if (!empty($this->with)) { - $this->fetchRelatedModels($models, $this->with); - } - return $models; - } else { - return array(); - } - } - - /** - * Executes query and returns a single row of result. - * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], - * the query result may be either an array or an ActiveRecord object. Null will be returned - * if the query results in nothing. - */ - public function one() - { - $command = $this->createCommand(); - $row = $command->queryRow(); - if ($row !== false && !$this->asArray) { - /** @var $class ActiveRecord */ - $class = $this->modelClass; - $model = $class::create($row); - if (!empty($this->with)) { - $models = array($model); - $this->fetchRelatedModels($models, $this->with); - $model = $models[0]; - } - return $model; - } else { - return $row === false ? null : $row; - } - } - - /** - * Returns a scalar value for this query. - * The value returned will be the first column in the first row of the query results. - * @return string|boolean the value of the first column in the first row of the query result. - * False is returned if there is no value. - */ - public function value() - { - return $this->createCommand()->queryScalar(); - } - - /** - * Executes query and returns if matching row exists in the table. - * @return bool if row exists in the table. - */ - public function exists() - { - $this->select = array(new Expression('1')); - return $this->value() !== false; - } - - /** - * Creates a DB command that can be used to execute this query. - * @return Command the created DB command instance. - */ - public function createCommand() - { - /** @var $modelClass ActiveRecord */ - $modelClass = $this->modelClass; - $db = $modelClass::getDbConnection(); - if ($this->sql === null) { - if ($this->from === null) { - $tableName = $modelClass::tableName(); - $this->from = array($tableName); - } - if (!empty($this->scopes)) { - foreach ($this->scopes as $name => $config) { - if (is_integer($name)) { - $modelClass::$config($this); - } else { - array_unshift($config, $this); - call_user_func_array(array($modelClass, $name), $config); - } - } - } - /** @var $qb QueryBuilder */ - $qb = $db->getQueryBuilder(); - $this->sql = $qb->build($this); - } - return $db->createCommand($this->sql, $this->params); - } - - public function asArray($value = true) - { - $this->asArray = $value; - return $this; - } - - public function with() - { - $this->with = func_get_args(); - if (isset($this->with[0]) && is_array($this->with[0])) { - // the parameter is given as an array - $this->with = $this->with[0]; - } - return $this; - } - - public function indexBy($column) - { - $this->indexBy = $column; - return $this; - } - - public function scopes($names) - { - $this->scopes = $names; - return $this; - } - - protected function createModels($rows) - { - $models = array(); - if ($this->asArray) { - if ($this->indexBy === null) { - return $rows; - } - foreach ($rows as $row) { - $models[$row[$this->indexBy]] = $row; - } - } else { - /** @var $class ActiveRecord */ - $class = $this->modelClass; - if ($this->indexBy === null) { - foreach ($rows as $row) { - $models[] = $class::create($row); - } - } else { - foreach ($rows as $row) { - $model = $class::create($row); - $models[$model->{$this->indexBy}] = $model; - } - } - } - return $models; - } - - protected function fetchRelatedModels(&$models, $with) - { - $primaryModel = new $this->modelClass; - $relations = $this->normalizeRelations($primaryModel, $with); - foreach ($relations as $name => $relation) { - if ($relation->asArray === null) { - // inherit asArray from primary query - $relation->asArray = $this->asArray; - } - $relation->findWith($name, $models); - } - } - - /** - * @param ActiveRecord $model - * @param array $with - * @return ActiveRelation[] - * @throws \yii\db\Exception - */ - protected function normalizeRelations($model, $with) - { - $relations = array(); - foreach ($with as $name => $options) { - if (is_integer($name)) { - $name = $options; - $options = array(); - } - if (($pos = strpos($name, '.')) !== false) { - // with sub-relations - $childName = substr($name, $pos + 1); - $name = substr($name, 0, $pos); - } else { - $childName = null; - } - - if (!isset($relations[$name])) { - if (!method_exists($model, $name)) { - throw new Exception("Unknown relation: $name"); - } - /** @var $relation ActiveRelation */ - $relation = $model->$name(); - $relation->primaryModel = null; - $relations[$name] = $relation; - } else { - $relation = $relations[$name]; - } - - if (isset($childName)) { - if (isset($relation->with[$childName])) { - $relation->with[$childName] = array_merge($relation->with, $options); - } else { - $relation->with[$childName] = $options; - } - } else { - foreach ($options as $p => $v) { - $relation->$p = $v; - } - } - } - return $relations; - } -} diff --git a/framework/db/ar/ActiveRecord.php b/framework/db/ar/ActiveRecord.php deleted file mode 100644 index 9685909..0000000 --- a/framework/db/ar/ActiveRecord.php +++ /dev/null @@ -1,926 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2012 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\db\ar; - -use yii\base\Model; -use yii\base\Event; -use yii\base\ModelEvent; -use yii\db\Exception; -use yii\db\dao\Connection; -use yii\db\dao\TableSchema; -use yii\db\dao\Query; -use yii\db\dao\Expression; -use yii\util\StringHelper; - -/** - * ActiveRecord is the base class for classes representing relational data. - * - * @author Qiang Xue - * @since 2.0 - * - * @property array $attributes attribute values indexed by attribute names - * - * ActiveRecord provides a set of events for further customization: - * - * - `beforeInsert`. Raised before the record is saved. - * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped. - * - `afterInsert`. Raised after the record is saved. - * - `beforeUpdate`. Raised before the record is saved. - * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped. - * - `afterUpdate`. Raised after the record is saved. - * - `beforeDelete`. Raised before the record is deleted. - * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped. - * - `afterDelete`. Raised after the record is deleted. - * - */ -abstract class ActiveRecord extends Model -{ - /** - * @var array attribute values indexed by attribute names - */ - private $_attributes = array(); - /** - * @var array old attribute values indexed by attribute names. - */ - private $_oldAttributes; - /** - * @var array related models indexed by the relation names - */ - private $_related; - - - /** - * Returns the database connection used by this AR class. - * By default, the "db" application component is used as the database connection. - * You may override this method if you want to use a different database connection. - * @return Connection the database connection used by this AR class. - */ - public static function getDbConnection() - { - return \Yii::$application->getDb(); - } - - /** - * Creates an [[ActiveQuery]] instance for query purpose. - * - * Because [[ActiveQuery]] implements a set of query building methods, - * additional query conditions can be specified by calling the methods of [[ActiveQuery]]. - * - * Below are some examples: - * - * ~~~ - * // find all customers - * $customers = Customer::find()->all(); - * // find a single customer whose primary key value is 10 - * $customer = Customer::find(10); - * // the above is equivalent to: - * Customer::find()->where(array('id' => 10))->one(); - * // find all active customers and order them by their age: - * $customers = Customer::find() - * ->where(array('status' => 1)) - * ->orderBy('age') - * ->all(); - * // or alternatively: - * $customers = Customer::find(array( - * 'where' => array('status' => 1), - * 'orderBy' => 'age', - * ))->all(); - * ~~~ - * - * @param mixed $q the query parameter. This can be one of the followings: - * - * - a scalar value (integer or string): query by a single primary key value and return the - * corresponding record. - * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object. - * - * @return ActiveQuery|ActiveRecord|boolean the [[ActiveQuery]] instance for query purpose, or - * the ActiveRecord object when a scalar is passed to this method which is considered to be a - * primary key value (false will be returned if no record is found in this case.) - */ - public static function find($q = null) - { - $query = static::createQuery(); - if (is_array($q)) { - foreach ($q as $name => $value) { - $query->$name = $value; - } - } elseif ($q !== null) { - // query by primary key - $primaryKey = static::primaryKey(); - return $query->where(array($primaryKey[0] => $q))->one(); - } - return $query; - } - - /** - * Creates an [[ActiveQuery]] instance and queries by a given SQL statement. - * Note that because the SQL statement is already specified, calling further - * query methods (such as `where()`, `order()`) on [[ActiveQuery]] will have no effect. - * Methods such as `with()`, `asArray()` can still be called though. - * @param string $sql the SQL statement to be executed - * @param array $params parameters to be bound to the SQL statement during execution. - * @return ActiveQuery the [[ActiveQuery]] instance - */ - public static function findBySql($sql, $params = array()) - { - $query = static::createQuery(); - $query->sql = $sql; - return $query->params($params); - } - - /** - * Performs a COUNT query for this AR class. - * - * Below are some usage examples: - * - * ~~~ - * // count the total number of customers - * echo Customer::count()->value(); - * // count the number of active customers: - * echo Customer::count(array( - * 'where' => array('status' => 1), - * ))->value(); - * // equivalent usage: - * echo Customer::count() - * ->where(array('status' => 1)) - * ->value(); - * // customize the count option - * echo Customer::count('COUNT(DISTINCT age)')->value(); - * ~~~ - * - * @param array|string $q the query option. This can be one of the followings: - * - * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object. - * - a string: the count expression, e.g. 'COUNT(DISTINCT age)'. - * - * @return ActiveQuery the [[ActiveQuery]] instance - */ - public static function count($q = null) - { - $query = static::createQuery(); - if (is_array($q)) { - foreach ($q as $name => $value) { - $query->$name = $value; - } - } elseif ($q !== null) { - $query->select = array($q); - } - if ($query->select === null) { - $query->select = array('COUNT(*)'); - } - return $query; - } - - /** - * Updates the whole table using the provided attribute values and conditions. - * @param array $attributes attribute values to be saved into the table - * @param string|array $condition the conditions that will be put in the WHERE part. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return integer the number of rows updated - */ - public static function updateAll($attributes, $condition = '', $params = array()) - { - $query = new Query; - $query->update(static::tableName(), $attributes, $condition, $params); - return $query->createCommand(static::getDbConnection())->execute(); - } - - /** - * Updates the whole table using the provided counter values and conditions. - * @param array $counters the counters to be updated (attribute name => increment value). - * @param string|array $condition the conditions that will be put in the WHERE part. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return integer the number of rows updated - */ - public static function updateAllCounters($counters, $condition = '', $params = array()) - { - $db = static::getDbConnection(); - foreach ($counters as $name => $value) { - $value = (int)$value; - $quotedName = $db->quoteColumnName($name, true); - $counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value"); - } - $query = new Query; - $query->update(static::tableName(), $counters, $condition, $params); - return $query->createCommand($db)->execute(); - } - - /** - * Deletes rows in the table using the provided conditions. - * @param string|array $condition the conditions that will be put in the WHERE part. - * Please refer to [[Query::where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return integer the number of rows updated - */ - public static function deleteAll($condition = '', $params = array()) - { - $query = new Query; - $query->delete(static::tableName(), $condition, $params); - return $query->createCommand(static::getDbConnection())->execute(); - } - - /** - * Creates a [[ActiveQuery]] instance. - * This method is called by [[find()]] and [[findBySql()]] to start a SELECT query. - * @return ActiveQuery the newly created [[ActiveQuery]] instance. - */ - public static function createQuery() - { - return new ActiveQuery(array('modelClass' => get_called_class())); - } - - /** - * Declares the name of the database table associated with this AR class. - * By default this method returns the class name as the table name by calling [[StringHelper::camel2id()]]. - * For example, 'Customer' becomes 'customer', and 'OrderDetail' becomes 'order_detail'. - * You may override this method if the table is not named after this convention. - * @return string the table name - */ - public static function tableName() - { - return StringHelper::camel2id(basename(get_called_class()), '_'); - } - - /** - * Returns the schema information of the DB table associated with this AR class. - * @return TableSchema the schema information of the DB table associated with this AR class. - */ - public static function getTableSchema() - { - return static::getDbConnection()->getTableSchema(static::tableName()); - } - - /** - * Returns the primary keys for this AR class. - * The default implementation will return the primary keys as declared - * in the DB table that is associated with this AR class. - * If the DB table does not declare any primary key, you should override - * this method to return the attributes that you want to use as primary keys - * for this AR class. - * @return string[] the primary keys of the associated database table. - */ - public static function primaryKey() - { - return static::getTableSchema()->primaryKey; - } - - /** - * Returns the default named scope that should be implicitly applied to all queries for this model. - * Note, the default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries. - * The default implementation simply returns an empty array. You may override this method - * if the model needs to be queried with some default criteria (e.g. only non-deleted users should be returned). - * @param ActiveQuery - */ - public static function defaultScope($query) - { - // todo: should we drop this? - } - - /** - * PHP getter magic method. - * This method is overridden so that attributes and related objects can be accessed like properties. - * @param string $name property name - * @return mixed property value - * @see getAttribute - */ - public function __get($name) - { - if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { - return $this->_attributes[$name]; - } elseif (isset($this->getTableSchema()->columns[$name])) { - return null; - } elseif (method_exists($this, $name)) { - // related records - if (isset($this->_related[$name]) || isset($this->_related) && array_key_exists($name, $this->_related)) { - return $this->_related[$name]; - } else { - // lazy loading related records - /** @var $relation ActiveRelation */ - $relation = $this->$name(); - return $this->_related[$name] = $relation->multiple ? $relation->all() : $relation->one(); - } - } else { - return parent::__get($name); - } - } - - /** - * PHP setter magic method. - * This method is overridden so that AR attributes can be accessed like properties. - * @param string $name property name - * @param mixed $value property value - */ - public function __set($name, $value) - { - if (isset($this->getTableSchema()->columns[$name])) { - $this->_attributes[$name] = $value; - } elseif (method_exists($this, $name)) { - $this->_related[$name] = $value; - } else { - parent::__set($name, $value); - } - } - - /** - * Checks if a property value is null. - * This method overrides the parent implementation by checking - * if the named attribute is null or not. - * @param string $name the property name or the event name - * @return boolean whether the property value is null - */ - public function __isset($name) - { - if (isset($this->_attributes[$name]) || isset($this->_related[$name])) { - return true; - } elseif (isset($this->getTableSchema()->columns[$name]) || method_exists($this, $name)) { - return false; - } else { - return parent::__isset($name); - } - } - - /** - * Sets a component property to be null. - * This method overrides the parent implementation by clearing - * the specified attribute value. - * @param string $name the property name or the event name - */ - public function __unset($name) - { - if (isset($this->getTableSchema()->columns[$name])) { - unset($this->_attributes[$name]); - } elseif (method_exists($this, $name)) { - unset($this->_related[$name]); - } else { - parent::__unset($name); - } - } - - /** - * Declares a `has-one` relation. - * The declaration is returned in terms of an [[ActiveRelation]] instance - * through which the related record can be queried and retrieved back. - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the columns in the table associated with the `$class` model, while the values of the - * array refer to the corresponding columns in the table associated with this AR class. - * @param array $properties additional property values that should be used to - * initialize the newly created relation object. - * @return ActiveRelation the relation object. - */ - public function hasOne($class, $link, $properties = array()) - { - if (strpos($class, '\\') === false) { - $primaryClass = get_class($this); - if (($pos = strrpos($primaryClass, '\\')) !== false) { - $class = substr($primaryClass, 0, $pos + 1) . $class; - } - } - - $properties['modelClass'] = $class; - $properties['primaryModel'] = $this; - $properties['link'] = $link; - $properties['multiple'] = false; - return new ActiveRelation($properties); - } - - /** - * Declares a `has-many` relation. - * The declaration is returned in terms of an [[ActiveRelation]] instance - * through which the related record can be queried and retrieved back. - * @param string $class the class name of the related record - * @param array $link the primary-foreign key constraint. The keys of the array refer to - * the columns in the table associated with the `$class` model, while the values of the - * array refer to the corresponding columns in the table associated with this AR class. - * @param array $properties additional property values that should be used to - * initialize the newly created relation object. - * @return ActiveRelation the relation object. - */ - public function hasMany($class, $link, $properties = array()) - { - $relation = $this->hasOne($class, $link, $properties); - $relation->multiple = true; - return $relation; - } - - /** - * Initializes the internal storage for the relation. - * This method is internally used by [[ActiveQuery]] when populating relation data. - * @param ActiveRelation $relation the relation object - */ - public function initRelation($relation) - { - $this->_related[$relation->name] = $relation->hasMany ? array() : null; - } - - /** - * @param ActiveRelation $relation - * @param ActiveRecord $record - */ - public function addRelatedRecord($relation, $record) - { - if ($relation->hasMany) { - if ($relation->indexBy !== null) { - $this->_related[$relation->name][$record->{$relation->indexBy}] = $record; - } else { - $this->_related[$relation->name][] = $record; - } - } else { - $this->_related[$relation->name] = $record; - } - } - - /** - * Returns the related record(s). - * This method will return the related record(s) of the current record. - * If the relation is HAS_ONE or BELONGS_TO, it will return a single object - * or null if the object does not exist. - * If the relation is HAS_MANY or MANY_MANY, it will return an array of objects - * or an empty array. - * @param ActiveRelation|string $relation the relation object or the name of the relation - * @param array|\Closure $params additional parameters that customize the query conditions as specified in the relation declaration. - * @return mixed the related object(s). - * @throws Exception if the relation is not specified in [[relations()]]. - */ - public function findByRelation($relation, $params = array()) - { - if (is_string($relation)) { - $md = $this->getMetaData(); - if (!isset($md->relations[$relation])) { - throw new Exception(get_class($this) . ' has no relation named "' . $relation . '".'); - } - $relation = $md->relations[$relation]; - } - $relation = clone $relation; - if ($params instanceof \Closure) { - $params($relation); - } else { - foreach ($params as $name => $value) { - $relation->$name = $value; - } - } - - $finder = new ActiveFinder($this->getDbConnection()); - return $finder->findWithRecord($this, $relation); - } - - /** - * Returns the list of all attribute names of the model. - * The default implementation will return all column names of the table associated with this AR class. - * @return array list of attribute names. - */ - public function attributes() - { - return array_keys($this->getTableSchema()->columns); - } - - /** - * Returns the named attribute value. - * If this is a new record and the attribute is not set before, - * the default column value will be returned. - * If this record is the result of a query and the attribute is not loaded, - * null will be returned. - * You may also use $this->AttributeName to obtain the attribute value. - * @param string $name the attribute name - * @return mixed the attribute value. Null if the attribute is not set or does not exist. - * @see hasAttribute - */ - public function getAttribute($name) - { - return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - - /** - * Sets the named attribute value. - * You may also use $this->AttributeName to set the attribute value. - * @param string $name the attribute name - * @param mixed $value the attribute value. - * @see hasAttribute - */ - public function setAttribute($name, $value) - { - $this->_attributes[$name] = $value; - } - - /** - * Returns the attribute values that have been modified since they are loaded or saved most recently. - * @param string[]|null $names the names of the attributes whose values may be returned if they are - * changed recently. If null, [[attributes()]] will be used. - * @return array the changed attribute values (name-value pairs) - */ - public function getChangedAttributes($names = null) - { - if ($names === null) { - $names = $this->attributes(); - } - $names = array_flip($names); - $attributes = array(); - if ($this->_oldAttributes === null) { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name])) { - $attributes[$name] = $value; - } - } - } else { - foreach ($this->_attributes as $name => $value) { - if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { - $attributes[$name] = $value; - } - } - } - return $attributes; - } - - /** - * Saves the current record. - * - * The record is inserted as a row into the database table if its {@link isNewRecord} - * property is true (usually the case when the record is created using the 'new' - * operator). Otherwise, it will be used to update the corresponding row in the table - * (usually the case if the record is obtained using one of those 'find' methods.) - * - * Validation will be performed before saving the record. If the validation fails, - * the record will not be saved. You can call {@link getErrors()} to retrieve the - * validation errors. - * - * If the record is saved via insertion, its {@link isNewRecord} property will be - * set false, and its {@link scenario} property will be set to be 'update'. - * And if its primary key is auto-incremental and is not set before insertion, - * the primary key will be populated with the automatically generated key value. - * - * @param boolean $runValidation whether to perform validation before saving the record. - * If the validation fails, the record will not be saved to database. - * @param array $attributes list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the saving succeeds - */ - public function save($runValidation = true, $attributes = null) - { - if (!$runValidation || $this->validate($attributes)) { - return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes); - } - return false; - } - - /** - * Inserts a row into the table based on this active record attributes. - * If the table's primary key is auto-incremental and is null before insertion, - * it will be populated with the actual value after insertion. - * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. - * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false, - * and its {@link scenario} property will be set to be 'update'. - * @param array $attributes list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the attributes are valid and the record is inserted successfully. - * @throws Exception if the record is not new - */ - public function insert($attributes = null) - { - if ($this->beforeInsert()) { - $query = new Query; - $values = $this->getChangedAttributes($attributes); - $db = $this->getDbConnection(); - $command = $query->insert($this->tableName(), $values)->createCommand($db); - if ($command->execute()) { - $table = $this->getTableSchema(); - if ($table->sequenceName !== null) { - foreach ($table->primaryKey as $name) { - if (!isset($this->_attributes[$name])) { - $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName); - break; - } - } - } - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $value; - } - $this->afterInsert(); - return true; - } - } - return false; - } - - /** - * Updates the row represented by this active record. - * All loaded attributes will be saved to the database. - * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. - * @param array $attributes list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the update is successful - * @throws Exception if the record is new - */ - public function update($attributes = null) - { - if ($this->getIsNewRecord()) { - throw new Exception('The active record cannot be updated because it is new.'); - } - if ($this->beforeUpdate()) { - $values = $this->getChangedAttributes($attributes); - if ($values !== array()) { - $this->updateAll($values, $this->getOldPrimaryKey(true)); - foreach ($values as $name => $value) { - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - $this->afterUpdate(); - } - return true; - } else { - return false; - } - } - - /** - * Saves one or several counter columns for the current AR object. - * Note that this method differs from [[updateAllCounters()]] in that it only - * saves counters for the current AR object. - * - * An example usage is as follows: - * - * ~~~ - * $post = Post::find($id)->one(); - * $post->updateCounters(array('view_count' => 1)); - * ~~~ - * - * Use negative values if you want to decrease the counters. - * @param array $counters the counters to be updated (attribute name => increment value) - * @return boolean whether the saving is successful - * @throws Exception if the record is new or any database error - * @see updateAllCounters() - */ - public function updateCounters($counters) - { - if ($this->getIsNewRecord()) { - throw new Exception('The active record cannot be updated because it is new.'); - } - $this->updateAllCounters($counters, $this->getOldPrimaryKey(true)); - foreach ($counters as $name => $value) { - $this->_attributes[$name] += $value; - $this->_oldAttributes[$name] = $this->_attributes[$name]; - } - return true; - } - - /** - * Deletes the row corresponding to this active record. - * @return boolean whether the deletion is successful. - * @throws Exception if the record is new or any database error - */ - public function delete() - { - if ($this->getIsNewRecord()) { - throw new Exception('The active record cannot be deleted because it is new.'); - } - if ($this->beforeDelete()) { - $result = $this->deleteAll($this->getPrimaryKey(true)) > 0; - $this->_oldAttributes = null; - $this->afterDelete(); - return $result; - } else { - return false; - } - } - - /** - * Returns if the current record is new. - * @return boolean whether the record is new and should be inserted when calling {@link save}. - * This property is automatically set in constructor and {@link populateRecord}. - * Defaults to false, but it will be set to true if the instance is created using - * the new operator. - */ - public function getIsNewRecord() - { - return $this->_oldAttributes === null; - } - - /** - * Sets if the record is new. - * @param boolean $value whether the record is new and should be inserted when calling {@link save}. - * @see getIsNewRecord - */ - public function setIsNewRecord($value) - { - $this->_oldAttributes = $value ? null : $this->_attributes; - } - - /** - * This method is invoked before saving a record (after validation, if any). - * The default implementation raises the `beforeSave` event. - * You may override this method to do any preparation work for record saving. - * Use {@link isNewRecord} to determine whether the saving is - * for inserting or updating record. - * Make sure you call the parent implementation so that the event is raised properly. - * @return boolean whether the saving should be executed. Defaults to true. - */ - public function beforeInsert() - { - $event = new ModelEvent($this); - $this->trigger('beforeInsert', $event); - return $event->isValid; - } - - /** - * This method is invoked after saving a record successfully. - * The default implementation raises the `afterSave` event. - * You may override this method to do postprocessing after record saving. - * Make sure you call the parent implementation so that the event is raised properly. - */ - public function afterInsert() - { - $this->trigger('afterInsert', new Event($this)); - } - - /** - * This method is invoked before saving a record (after validation, if any). - * The default implementation raises the `beforeSave` event. - * You may override this method to do any preparation work for record saving. - * Use {@link isNewRecord} to determine whether the saving is - * for inserting or updating record. - * Make sure you call the parent implementation so that the event is raised properly. - * @return boolean whether the saving should be executed. Defaults to true. - */ - public function beforeUpdate() - { - $event = new ModelEvent($this); - $this->trigger('beforeUpdate', $event); - return $event->isValid; - } - - /** - * This method is invoked after saving a record successfully. - * The default implementation raises the `afterSave` event. - * You may override this method to do postprocessing after record saving. - * Make sure you call the parent implementation so that the event is raised properly. - */ - public function afterUpdate() - { - $this->trigger('afterUpdate', new Event($this)); - } - - /** - * This method is invoked before deleting a record. - * The default implementation raises the `beforeDelete` event. - * You may override this method to do any preparation work for record deletion. - * Make sure you call the parent implementation so that the event is raised properly. - * @return boolean whether the record should be deleted. Defaults to true. - */ - public function beforeDelete() - { - $event = new ModelEvent($this); - $this->trigger('beforeDelete', $event); - return $event->isValid; - } - - /** - * This method is invoked after deleting a record. - * The default implementation raises the `afterDelete` event. - * You may override this method to do postprocessing after the record is deleted. - * Make sure you call the parent implementation so that the event is raised properly. - */ - public function afterDelete() - { - $this->trigger('afterDelete', new Event($this)); - } - - /** - * Repopulates this active record with the latest data. - * @param array $attributes - * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record. - */ - public function refresh($attributes = null) - { - if ($this->getIsNewRecord()) { - return false; - } - $record = $this->find()->where($this->getPrimaryKey(true))->one(); - if ($record === null) { - return false; - } - if ($attributes === null) { - foreach ($this->attributes() as $name) { - $this->_attributes[$name] = $record->_attributes[$name]; - } - $this->_oldAttributes = $this->_attributes; - } else { - foreach ($attributes as $name) { - $this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name]; - } - } - return true; - } - - /** - * 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 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) - { - return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); - } - - /** - * Returns the primary key value. - * @param boolean $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with column name as key and column value as value. - * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite. - * If primary key is not defined, null will be returned. - */ - public function getPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (count($keys) === 1 && !$asArray) { - return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; - } else { - $values = array(); - foreach ($keys as $name) { - $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; - } - return $values; - } - } - - /** - * Returns the old primary key value. - * This refers to the primary key value that is populated into the record - * after executing a find method (e.g. find(), findAll()). - * The value remains unchanged even if the primary key attribute is manually assigned with a different value. - * @param boolean $asArray whether to return the primary key value as an array. If true, - * the return value will be an array with column name as key and column value as value. - * If this is false (default), a scalar value will be returned for non-composite primary key. - * @return string|array the old primary key value. An array (column name=>column value) is returned if the primary key is composite. - * If primary key is not defined, null will be returned. - */ - public function getOldPrimaryKey($asArray = false) - { - $keys = $this->primaryKey(); - if (count($keys) === 1 && !$asArray) { - return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; - } else { - $values = array(); - foreach ($keys as $name) { - $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; - } - return $values; - } - } - - /** - * Creates an active record with the given attributes. - * @param array $row attribute values (name => value) - * @return ActiveRecord the newly created active record. - */ - public static function create($row) - { - $record = static::instantiate($row); - $columns = static::getTableSchema()->columns; - foreach ($row as $name => $value) { - if (isset($columns[$name])) { - $record->_attributes[$name] = $value; - } else { - $record->$name = $value; - } - } - $record->_oldAttributes = $record->_attributes; - return $record; - } - - /** - * Creates an active record instance. - * This method is called by [[createRecord()]]. - * You may override this method if the instance being created - * depends the attributes that are to be populated to the record. - * For example, by creating a record based on the value of a column, - * you may implement the so-called single-table inheritance mapping. - * @param array $row list of attribute values for the active records. - * @return ActiveRecord the active record - */ - public static function instantiate($row) - { - return new static; - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the interface ArrayAccess. - * @param mixed $offset the offset to check on - * @return boolean - */ - public function offsetExists($offset) - { - return $this->__isset($offset); - } - - -} diff --git a/framework/db/ar/ActiveRecordBehavior.php b/framework/db/ar/ActiveRecordBehavior.php deleted file mode 100644 index b5d9305..0000000 --- a/framework/db/ar/ActiveRecordBehavior.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @since 2.0 - */ -class ActiveRecordBehavior extends ModelBehavior -{ - /** - * Declares events and the corresponding event handler methods. - * If you override this method, make sure you merge the parent result to the return value. - * @return array events (array keys) and the corresponding event handler methods (array values). - * @see \yii\base\Behavior::events() - */ - public function events() - { - return array_merge(parent::events(), array( - 'beforeInsert' => 'beforeInsert', - 'afterInsert' => 'afterInsert', - 'beforeUpdate' => 'beforeUpdate', - 'afterUpdate' => 'afterUpdate', - 'beforeDelete' => 'beforeDelete', - 'afterDelete' => 'afterDelete', - )); - } - - /** - * Responds to the owner's `beforeInsert` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter - * to be false to quit the ActiveRecord inserting process. - * @param \yii\base\ModelEvent $event event parameter - */ - public function beforeInsert($event) - { - } - - /** - * Responds to the owner's `afterInsert` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * @param \yii\base\ModelEvent $event event parameter - */ - public function afterInsert($event) - { - } - - /** - * Responds to the owner's `beforeUpdate` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter - * to be false to quit the ActiveRecord updating process. - * @param \yii\base\ModelEvent $event event parameter - */ - public function beforeUpdate($event) - { - } - - /** - * Responds to the owner's `afterUpdate` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * @param \yii\base\ModelEvent $event event parameter - */ - public function afterUpdate($event) - { - } - - /** - * Responds to the owner's `beforeDelete` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * You may set the [[ModelEvent::isValid|isValid]] property of the event parameter - * to be false to quit the ActiveRecord deleting process. - * @param \yii\base\ModelEvent $event event parameter - */ - public function beforeDelete($event) - { - } - - /** - * Responds to the owner's `afterDelete` event. - * Overrides this method if you want to handle the corresponding event of the owner. - * @param \yii\base\ModelEvent $event event parameter - */ - public function afterDelete($event) - { - } -} diff --git a/framework/db/ar/ActiveRelation.php b/framework/db/ar/ActiveRelation.php deleted file mode 100644 index 64f15a2..0000000 --- a/framework/db/ar/ActiveRelation.php +++ /dev/null @@ -1,269 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2012 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\db\ar; - -use yii\db\dao\Connection; -use yii\db\dao\Command; -use yii\db\dao\QueryBuilder; - -/** - * It is used in three scenarios: - * - eager loading: User::find()->with('posts')->all(); - * - lazy loading: $user->posts; - * - lazy loading with query options: $user->posts()->where('status=1')->get(); - * - * @author Qiang Xue - * @since 2.0 - */ -class ActiveRelation extends ActiveQuery -{ - /** - * @var boolean whether this relation should populate all query results into AR instances. - * If false, only the first row of the results will be taken. - */ - public $multiple; - /** - * @var ActiveRecord the primary model that this relation is associated with. - * This is used only in lazy loading with dynamic query options. - */ - public $primaryModel; - /** - * @var array the columns of the primary and foreign tables that establish the relation. - * The array keys must be columns of the table for this relation, and the array values - * must be the corresponding columns from the primary table. - * Do not prefix or quote the column names as they will be done automatically by Yii. - */ - public $link; - /** - * @var array|ActiveRelation - */ - public $via; - - /** - * @param string $relationName - * @param array|\Closure $options - * @return ActiveRelation - */ - public function via($relationName, $options = null) - { - /** @var $relation ActiveRelation */ - $relation = $this->primaryModel->$relationName(); - $relation->primaryModel = null; - $this->via = array($relationName, $relation); - if (is_array($options)) { - foreach ($options as $name => $value) { - $this->$name = $value; - } - } elseif ($options instanceof \Closure) { - $options($relation); - } - return $this; - } - - /** - * @param string $tableName - * @param array $link - * @param array|\Closure $options - * @return ActiveRelation - */ - public function viaTable($tableName, $link, $options = null) - { - $relation = new ActiveRelation(array( - 'modelClass' => get_class($this->primaryModel), - 'from' => array($tableName), - 'link' => $link, - 'multiple' => true, - 'asArray' => true, - )); - $this->via = $relation; - if (is_array($options)) { - foreach ($options as $name => $value) { - $this->$name = $value; - } - } elseif ($options instanceof \Closure) { - $options($relation); - } - return $this; - } - - /** - * Creates a DB command that can be used to execute this query. - * @return Command the created DB command instance. - */ - public function createCommand() - { - if ($this->primaryModel !== null) { - // lazy loading - if ($this->via instanceof self) { - // via pivot table - $viaModels = $this->via->findPivotRows(array($this->primaryModel)); - $this->filterByModels($viaModels); - } elseif (is_array($this->via)) { - // via relation - $relationName = $this->via[0]; - $viaModels = $this->primaryModel->$relationName; - if ($viaModels === null) { - $viaModels = array(); - } elseif (!is_array($viaModels)) { - $viaModels = array($viaModels); - } - $this->filterByModels($viaModels); - } else { - $this->filterByModels(array($this->primaryModel)); - } - } - return parent::createCommand(); - } - - public function findWith($name, &$primaryModels) - { - if (!is_array($this->link)) { - throw new \yii\base\Exception('invalid link'); - } - - if ($this->via instanceof self) { - // via pivot table - /** @var $viaQuery ActiveRelation */ - $viaQuery = $this->via; - $viaModels = $viaQuery->findPivotRows($primaryModels); - $this->filterByModels($viaModels); - } elseif (is_array($this->via)) { - // via relation - /** @var $viaQuery ActiveRelation */ - list($viaName, $viaQuery) = $this->via; - $viaModels = $viaQuery->findWith($viaName, $primaryModels); - $this->filterByModels($viaModels); - } else { - $this->filterByModels($primaryModels); - } - - if (count($primaryModels) === 1 && !$this->multiple) { - $model = $this->one(); - foreach ($primaryModels as $i => $primaryModel) { - $primaryModels[$i][$name] = $model; - } - return array($model); - } else { - $models = $this->all(); - if (isset($viaModels, $viaQuery)) { - $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link); - } else { - $buckets = $this->buildBuckets($models, $this->link); - } - - $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); - foreach ($primaryModels as $i => $primaryModel) { - $key = $this->getModelKey($primaryModel, $link); - if (isset($buckets[$key])) { - $primaryModels[$i][$name] = $buckets[$key]; - } else { - $primaryModels[$i][$name] = $this->multiple ? array() : null; - } - } - return $models; - } - } - - protected function buildBuckets($models, $link, $viaModels = null, $viaLink = null) - { - $buckets = array(); - $linkKeys = array_keys($link); - foreach ($models as $i => $model) { - $key = $this->getModelKey($model, $linkKeys); - if ($this->indexBy !== null) { - $buckets[$key][$i] = $model; - } else { - $buckets[$key][] = $model; - } - } - - if ($viaModels !== null) { - $viaBuckets = array(); - $viaLinkKeys = array_keys($viaLink); - $linkValues = array_values($link); - foreach ($viaModels as $viaModel) { - $key1 = $this->getModelKey($viaModel, $viaLinkKeys); - $key2 = $this->getModelKey($viaModel, $linkValues); - if (isset($buckets[$key2])) { - foreach ($buckets[$key2] as $i => $bucket) { - if ($this->indexBy !== null) { - $viaBuckets[$key1][$i] = $bucket; - } else { - $viaBuckets[$key1][] = $bucket; - } - } - } - } - $buckets = $viaBuckets; - } - - if (!$this->multiple) { - foreach ($buckets as $i => $bucket) { - $buckets[$i] = reset($bucket); - } - } - return $buckets; - } - - protected function getModelKey($model, $attributes) - { - if (count($attributes) > 1) { - $key = array(); - foreach ($attributes as $attribute) { - $key[] = $model[$attribute]; - } - return serialize($key); - } else { - $attribute = reset($attributes); - return $model[$attribute]; - } - } - - protected function filterByModels($models) - { - $attributes = array_keys($this->link); - $values = array(); - if (count($attributes) ===1) { - // single key - $attribute = reset($this->link); - foreach ($models as $model) { - $values[] = $model[$attribute]; - } - } else { - // composite keys - foreach ($models as $model) { - $v = array(); - foreach ($this->link as $attribute => $link) { - $v[$attribute] = $model[$link]; - } - $values[] = $v; - } - } - $this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR))); - } - - /** - * @param ActiveRecord[] $primaryModels - * @return array - */ - protected function findPivotRows($primaryModels) - { - if (empty($primaryModels)) { - return array(); - } - $this->filterByModels($primaryModels); - /** @var $primaryModel ActiveRecord */ - $primaryModel = reset($primaryModels); - $db = $primaryModel->getDbConnection(); - $sql = $db->getQueryBuilder()->build($this); - return $db->createCommand($sql, $this->params)->queryAll(); - } -} diff --git a/framework/db/dao/BaseQuery.php b/framework/db/dao/BaseQuery.php deleted file mode 100644 index f91beb7..0000000 --- a/framework/db/dao/BaseQuery.php +++ /dev/null @@ -1,555 +0,0 @@ - - * @since 2.0 - */ -class BaseQuery extends \yii\base\Component -{ - /** - * @var string|array the columns being selected. This refers to the SELECT clause in a SQL - * statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`). - * If not set, if means all columns. - * @see select() - */ - public $select; - /** - * @var string additional option that should be appended to the 'SELECT' keyword. For example, - * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. - */ - public $selectOption; - /** - * @var boolean whether to select distinct rows of data only. If this is set true, - * the SELECT clause would be changed to SELECT DISTINCT. - */ - public $distinct; - /** - * @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement. - * It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`). - * @see from() - */ - public $from; - /** - * @var string|array query condition. This refers to the WHERE clause in a SQL statement. - * For example, `age > 31 AND team = 1`. - * @see where() - */ - public $where; - /** - * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. - */ - public $limit; - /** - * @var integer zero-based offset from where the records are to be returned. If not set or - * less than 0, it means starting from the beginning. - */ - public $offset; - /** - * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement. - * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`). - */ - public $orderBy; - /** - * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement. - * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`). - */ - public $groupBy; - /** - * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement. - * It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g. - * `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`). - * @see join() - */ - public $join; - /** - * @var string|array the condition to be applied in the GROUP BY clause. - * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition. - */ - public $having; - /** - * @var string|BaseQuery[] the UNION clause(s) in a SQL statement. This can be either a string - * representing a single UNION clause or an array representing multiple UNION clauses. - * Each union clause can be a string or a `BaseQuery` object which refers to the SQL statement. - */ - public $union; - /** - * @var array list of query parameter values indexed by parameter placeholders. - * For example, `array(':name'=>'Dan', ':age'=>31)`. - */ - public $params; - - /** - * Sets the SELECT part of the query. - * @param string|array $columns the columns to be selected. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). - * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id"). - * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). - * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, - * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. - * @return BaseQuery the query object itself - */ - public function select($columns, $option = null) - { - $this->select = $columns; - $this->selectOption = $option; - return $this; - } - - /** - * Sets the value indicating whether to SELECT DISTINCT or not. - * @param bool $value whether to SELECT DISTINCT or not. - * @return BaseQuery the query object itself - */ - public function distinct($value = true) - { - $this->distinct = $value; - return $this; - } - - /** - * Sets the FROM part of the query. - * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`) - * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names. - * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`). - * The method will automatically quote the table names unless it contains some parenthesis - * (which means the table is given as a sub-query or DB expression). - * @return BaseQuery the query object itself - */ - public function from($tables) - { - $this->from = $tables; - return $this; - } - - /** - * Sets the WHERE part of the query. - * - * The method requires a $condition parameter, and optionally a $params parameter - * specifying the values to be bound to the query. - * - * The $condition parameter should be either a string (e.g. 'id=1') or an array. - * If the latter, it must be in one of the following two formats: - * - * - hash format: `array('column1' => value1, 'column2' => value2, ...)` - * - operator format: `array(operator, operand1, operand2, ...)` - * - * A condition in hash format represents the following SQL expression in general: - * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, - * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used - * in the generated expression. Below are some examples: - * - * - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`. - * - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`. - * - `array('status'=>null) generates `status IS NULL`. - * - * A condition in operator format generates the SQL expression according to the specified operator, which - * can be one of the followings: - * - * - `and`: the operands should be concatenated together using `AND`. For example, - * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array, - * it will be converted into a string using the rules described here. For example, - * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`. - * The method will NOT do any quoting or escaping. - * - * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`. - * - * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the - * starting and ending values of the range that the column is in. - * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`. - * - * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` - * in the generated condition. - * - * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing - * the range of the values that the column or DB expression should be in. For example, - * `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`. - * The method will properly quote the column name and escape values in the range. - * - * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. - * - * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing - * the values that the column or DB expression should be like. - * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`. - * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate - * `name LIKE '%test%' AND name LIKE '%sample%'`. - * The method will properly quote the column name and escape values in the range. - * - * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` - * predicates when operand 2 is an array. - * - * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` - * in the generated condition. - * - * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate - * the `NOT LIKE` predicates. - * - * @param string|array $condition the conditions that should be put in the WHERE part. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see andWhere() - * @see orWhere() - */ - public function where($condition, $params = array()) - { - $this->where = $condition; - $this->addParams($params); - return $this; - } - - /** - * Adds an additional WHERE condition to the existing one. - * The new condition and the existing one will be joined using the 'AND' operator. - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see where() - * @see orWhere() - */ - public function andWhere($condition, $params = array()) - { - if ($this->where === null) { - $this->where = $condition; - } else { - $this->where = array('and', $this->where, $condition); - } - $this->addParams($params); - return $this; - } - - /** - * Adds an additional WHERE condition to the existing one. - * The new condition and the existing one will be joined using the 'OR' operator. - * @param string|array $condition the new WHERE condition. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see where() - * @see andWhere() - */ - public function orWhere($condition, $params = array()) - { - if ($this->where === null) { - $this->where = $condition; - } else { - $this->where = array('or', $this->where, $condition); - } - $this->addParams($params); - return $this; - } - - /** - * Appends a JOIN part to the query. - * The first parameter specifies what type of join it is. - * @param string $type the type of join, such as INNER JOIN, LEFT JOIN. - * @param string $table the table to be joined. - * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). - * The method will automatically quote the table name unless it contains some parenthesis - * (which means the table is given as a sub-query or DB expression). - * @param string|array $on the join condition that should appear in the ON part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - */ - public function join($type, $table, $on = '', $params = array()) - { - $this->join[] = array($type, $table, $on); - return $this->addParams($params); - } - - /** - * Appends an INNER JOIN part to the query. - * @param string $table the table to be joined. - * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). - * The method will automatically quote the table name unless it contains some parenthesis - * (which means the table is given as a sub-query or DB expression). - * @param string|array $on the join condition that should appear in the ON part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - */ - public function innerJoin($table, $on = '', $params = array()) - { - $this->join[] = array('INNER JOIN', $table, $on); - return $this->addParams($params); - } - - /** - * Appends a LEFT OUTER JOIN part to the query. - * @param string $table the table to be joined. - * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). - * The method will automatically quote the table name unless it contains some parenthesis - * (which means the table is given as a sub-query or DB expression). - * @param string|array $on the join condition that should appear in the ON part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query - * @return BaseQuery the query object itself - */ - public function leftJoin($table, $on = '', $params = array()) - { - $this->join[] = array('LEFT JOIN', $table, $on); - return $this->addParams($params); - } - - /** - * Appends a RIGHT OUTER JOIN part to the query. - * @param string $table the table to be joined. - * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). - * The method will automatically quote the table name unless it contains some parenthesis - * (which means the table is given as a sub-query or DB expression). - * @param string|array $on the join condition that should appear in the ON part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query - * @return BaseQuery the query object itself - */ - public function rightJoin($table, $on = '', $params = array()) - { - $this->join[] = array('RIGHT JOIN', $table, $on); - return $this->addParams($params); - } - - /** - * Sets the GROUP BY part of the query. - * @param string|array $columns the columns to be grouped by. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). - * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). - * @return BaseQuery the query object itself - * @see addGroup() - */ - public function groupBy($columns) - { - $this->groupBy = $columns; - return $this; - } - - /** - * Adds additional group-by columns to the existing ones. - * @param string|array $columns additional columns to be grouped by. - * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). - * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). - * @return BaseQuery the query object itself - * @see group() - */ - public function addGroup($columns) - { - if (empty($this->groupBy)) { - $this->groupBy = $columns; - } else { - if (!is_array($this->groupBy)) { - $this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY); - } - if (!is_array($columns)) { - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - } - $this->groupBy = array_merge($this->groupBy, $columns); - } - return $this; - } - - /** - * Sets the HAVING part of the query. - * @param string|array $condition the conditions to be put after HAVING. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see andHaving() - * @see orHaving() - */ - public function having($condition, $params = array()) - { - $this->having = $condition; - $this->addParams($params); - return $this; - } - - /** - * Adds an additional HAVING condition to the existing one. - * The new condition and the existing one will be joined using the 'AND' operator. - * @param string|array $condition the new HAVING condition. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see having() - * @see orHaving() - */ - public function andHaving($condition, $params = array()) - { - if ($this->having === null) { - $this->having = $condition; - } else { - $this->having = array('and', $this->having, $condition); - } - $this->addParams($params); - return $this; - } - - /** - * Adds an additional HAVING condition to the existing one. - * The new condition and the existing one will be joined using the 'OR' operator. - * @param string|array $condition the new HAVING condition. Please refer to [[where()]] - * on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return BaseQuery the query object itself - * @see having() - * @see andHaving() - */ - public function orHaving($condition, $params = array()) - { - if ($this->having === null) { - $this->having = $condition; - } else { - $this->having = array('or', $this->having, $condition); - } - $this->addParams($params); - return $this; - } - - /** - * Sets the ORDER BY part of the query. - * @param string|array $columns the columns (and the directions) to be ordered by. - * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). - * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). - * @return BaseQuery the query object itself - * @see addOrder() - */ - public function orderBy($columns) - { - $this->orderBy = $columns; - return $this; - } - - /** - * Adds additional ORDER BY columns to the query. - * @param string|array $columns the columns (and the directions) to be ordered by. - * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). - * The method will automatically quote the column names unless a column contains some parenthesis - * (which means the column contains a DB expression). - * @return BaseQuery the query object itself - * @see order() - */ - public function addOrderBy($columns) - { - if (empty($this->orderBy)) { - $this->orderBy = $columns; - } else { - if (!is_array($this->orderBy)) { - $this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY); - } - if (!is_array($columns)) { - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - } - $this->orderBy = array_merge($this->orderBy, $columns); - } - return $this; - } - - /** - * Sets the LIMIT part of the query. - * @param integer $limit the limit - * @return BaseQuery the query object itself - */ - public function limit($limit) - { - $this->limit = $limit; - return $this; - } - - /** - * Sets the OFFSET part of the query. - * @param integer $offset the offset - * @return BaseQuery the query object itself - */ - public function offset($offset) - { - $this->offset = $offset; - return $this; - } - - /** - * Appends a SQL statement using UNION operator. - * @param string|BaseQuery $sql the SQL statement to be appended using UNION - * @return BaseQuery the query object itself - */ - public function union($sql) - { - $this->union[] = $sql; - return $this; - } - - /** - * Sets the parameters to be bound to the query. - * @param array $params list of query parameter values indexed by parameter placeholders. - * For example, `array(':name'=>'Dan', ':age'=>31)`. - * @return BaseQuery the query object itself - * @see addParams() - */ - public function params($params) - { - $this->params = $params; - return $this; - } - - /** - * Adds additional parameters to be bound to the query. - * @param array $params list of query parameter values indexed by parameter placeholders. - * For example, `array(':name'=>'Dan', ':age'=>31)`. - * @return BaseQuery the query object itself - * @see params() - */ - public function addParams($params) - { - if ($params !== array()) { - if ($this->params === null) { - $this->params = $params; - } else { - foreach ($params as $name => $value) { - if (is_integer($name)) { - $this->params[] = $value; - } else { - $this->params[$name] = $value; - } - } - } - } - return $this; - } - - /** - * Merges this query with another one. - * If a property of `$query` is not null, it will be used to overwrite - * the corresponding property of `$this`. - * @param BaseQuery $query the new query to be merged with this query. - * @return BaseQuery the query object itself - */ - public function mergeWith(BaseQuery $query) - { - $properties = array( - 'select', 'selectOption', 'distinct', 'from', - 'where', 'limit', 'offset', 'orderBy', 'groupBy', - 'join', 'having', 'union', 'params', - ); - // todo: incorrect, do we need it? should we provide a configure() method instead? - foreach ($properties as $name => $value) { - if ($value !== null) { - $this->$name = $value; - } - } - return $this; - } -} diff --git a/framework/db/dao/ColumnSchema.php b/framework/db/dao/ColumnSchema.php deleted file mode 100644 index 5f1fbdf..0000000 --- a/framework/db/dao/ColumnSchema.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @since 2.0 - */ -class ColumnSchema extends \yii\base\Component -{ - /** - * @var string name of this column (without quotes). - */ - public $name; - /** - * @var string the quoted name of this column. - */ - public $quotedName; - /** - * @var boolean whether this column can be null. - */ - public $allowNull; - /** - * @var string logical type of this column. Possible logic types include: - * string, text, boolean, smallint, integer, bigint, float, decimal, datetime, - * timestamp, time, date, binary, and money. - */ - public $type; - /** - * @var string the PHP type of this column. Possible PHP types include: - * string, boolean, integer, double. - */ - public $phpType; - /** - * @var string the DB type of this column. Possible DB types vary according to the type of DBMS. - */ - public $dbType; - /** - * @var mixed default value of this column - */ - public $defaultValue; - /** - * @var array enumerable values. This is set only if the column is declared to be an enumerable type. - */ - public $enumValues; - /** - * @var integer display size of the column. - */ - public $size; - /** - * @var integer precision of the column data, if it is numeric. - */ - public $precision; - /** - * @var integer scale of the column data, if it is numeric. - */ - public $scale; - /** - * @var boolean whether this column is a primary key - */ - public $isPrimaryKey; - /** - * @var boolean whether this column is auto-incremental - */ - public $autoIncrement = false; - /** - * @var boolean whether this column is unsigned. This is only meaningful - * when [[type]] is `smallint`, `integer` or `bigint`. - */ - public $unsigned; - - /** - * Extracts the PHP type from DB type. - */ - public function resolvePhpType() - { - static $typeMap = array( // logical type => php type - 'smallint' => 'integer', - 'integer' => 'integer', - 'bigint' => 'integer', - 'boolean' => 'boolean', - 'float' => 'double', - ); - if (isset($typeMap[$this->type])) { - if ($this->type === 'bigint') { - $this->phpType = PHP_INT_SIZE == 8 && !$this->unsigned ? 'integer' : 'string'; - } elseif ($this->type === 'integer') { - $this->phpType = PHP_INT_SIZE == 4 && $this->unsigned ? 'string' : 'integer'; - } else { - $this->phpType = $typeMap[$this->type]; - } - } else { - $this->phpType = 'string'; - } - } - - /** - * Converts the input value according to [[phpType]]. - * If the value is null or an [[Expression]], it will not be converted. - * @param mixed $value input value - * @return mixed converted value - */ - public function typecast($value) - { - if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { - return $value; - } - switch ($this->phpType) { - case 'string': - return (string)$value; - case 'integer': - return (integer)$value; - case 'boolean': - return (boolean)$value; - } - return $value; - } -} diff --git a/framework/db/dao/Command.php b/framework/db/dao/Command.php deleted file mode 100644 index 78b3581..0000000 --- a/framework/db/dao/Command.php +++ /dev/null @@ -1,424 +0,0 @@ -db->createCommand('SELECT * FROM tbl_user')->queryAll(); - * ~~~ - * - * Command supports SQL statement preparation and parameter binding. - * Call [[bindValue()]] to bind a value to a SQL parameter; - * Call [[bindParam()]] to bind a PHP variable to a SQL parameter. - * When binding a parameter, the SQL statement is automatically prepared. - * You may also call [[prepare()]] explicitly to prepare a SQL statement. - * - * @property string $sql the SQL statement to be executed - * - * @author Qiang Xue - * @since 2.0 - */ -class Command extends \yii\base\Component -{ - /** - * @var Connection the DB connection that this command is associated with - */ - public $connection; - /** - * @var \PDOStatement the PDOStatement object that this command contains - */ - public $pdoStatement; - /** - * @var mixed the default fetch mode for this command. - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - */ - public $fetchMode = \PDO::FETCH_ASSOC; - /** - * @var string the SQL statement that this command represents - */ - private $_sql; - /** - * @var array the parameter log information (name=>value) - */ - private $_params = array(); - - /** - * Constructor. - * @param Connection $connection the database connection - * @param string $sql the SQL statement to be executed - * @param array $params the parameters to be bound to the SQL statement - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($connection, $sql = null, $params = array(), $config = array()) - { - $this->connection = $connection; - $this->_sql = $sql; - $this->bindValues($params); - parent::__construct($config); - } - - /** - * Returns the SQL statement for this command. - * @return string the SQL statement to be executed - */ - public function getSql() - { - return $this->_sql; - } - - /** - * Specifies the SQL statement to be executed. - * Any previous execution will be terminated or cancelled. - * @param string $value the SQL statement to be set. - * @return Command this command instance - */ - public function setSql($value) - { - $this->_sql = $value; - $this->_params = array(); - $this->cancel(); - return $this; - } - - /** - * 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. - * @throws Exception if there is any DB error - */ - public function prepare() - { - if ($this->pdoStatement == null) { - $sql = $this->connection->expandTablePrefix($this->getSql()); - try { - $this->pdoStatement = $this->connection->pdo->prepare($sql); - } catch (\Exception $e) { - \Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__); - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($e->getMessage(), (int)$e->getCode(), $errorInfo); - } - } - } - - /** - * Cancels the execution of the SQL statement. - * This method mainly sets [[pdoStatement]] to be null. - */ - public function cancel() - { - $this->pdoStatement = null; - } - - /** - * Binds a parameter to the SQL statement to be executed. - * @param string|integer $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 - * @return Command the current command being executed - * @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->pdoStatement->bindParam($name, $value, $this->connection->getPdoType(gettype($value))); - } elseif ($length === null) { - $this->pdoStatement->bindParam($name, $value, $dataType); - } elseif ($driverOptions === null) { - $this->pdoStatement->bindParam($name, $value, $dataType, $length); - } else { - $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); - } - $this->_params[$name] =& $value; - return $this; - } - - /** - * Binds a value to a parameter. - * @param string|integer $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 Command the current command being executed - * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php - */ - public function bindValue($name, $value, $dataType = null) - { - $this->prepare(); - if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value))); - } else { - $this->pdoStatement->bindValue($name, $value, $dataType); - } - $this->_params[$name] = $value; - return $this; - } - - /** - * Binds a list of values to the corresponding parameters. - * This is similar to [[bindValue()]] except that it binds multiple values at a time. - * 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, - * e.g. `array(':name'=>'John', ':age'=>25)`. By default, the PDO type of each value is determined - * by its PHP type. You may explicitly specify the PDO type by using an array: `array(value, type)`, - * e.g. `array(':name'=>'John', ':profile'=>array($profile, \PDO::PARAM_LOB))`. - * @return Command the current command being executed - */ - public function bindValues($values) - { - if (!empty($values)) { - $this->prepare(); - foreach ($values as $name => $value) { - if (is_array($value)) { - $type = $value[1]; - $value = $value[0]; - } else { - $type = $this->connection->getPdoType(gettype($value)); - } - $this->pdoStatement->bindValue($name, $value, $type); - $this->_params[$name] = $value; - } - } - return $this; - } - - /** - * Executes the SQL statement. - * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. - * No result set will be returned. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return integer number of rows affected by the execution. - * @throws Exception execution failed - */ - public function execute($params = array()) - { - $sql = $this->connection->expandTablePrefix($this->getSql()); - $this->_params = array_merge($this->_params, $params); - if ($this->_params === array()) { - $paramLog = ''; - } else { - $paramLog = "\nParameters: " . var_export($this->_params, true); - } - - \Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__); - - try { - if ($this->connection->enableProfiling) { - \Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__); - } - - $this->prepare(); - if ($params === array()) { - $this->pdoStatement->execute(); - } else { - $this->pdoStatement->execute($params); - } - $n = $this->pdoStatement->rowCount(); - - if ($this->connection->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - return $n; - } catch (\Exception $e) { - if ($this->connection->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - $message = $e->getMessage(); - \Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__); - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($message, (int)$e->getCode(), $errorInfo); - } - } - - /** - * Executes the SQL statement and returns query result. - * This method is for executing a SQL query that returns result set, such as `SELECT`. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return DataReader the reader object for fetching the query result - * @throws Exception execution failed - */ - public function query($params = array()) - { - return $this->queryInternal('', $params); - } - - /** - * Executes the SQL statement and returns ALL rows at once. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array all rows of the query result. Each array element is an array representing a row of data. - * An empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryAll($params = array(), $fetchMode = null) - { - return $this->queryInternal('fetchAll', $params, $fetchMode); - } - - /** - * Executes the SQL statement and returns the first row of the result. - * This method is best used when only the first row of result is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query - * results in nothing. - * @throws Exception execution failed - */ - public function queryRow($params = array(), $fetchMode = null) - { - return $this->queryInternal('fetch', $params, $fetchMode); - } - - /** - * Executes the SQL statement and returns the value of the first column in the first row of data. - * This method is best used when only a single value is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return string|boolean the value of the first column in the first row of the query result. - * False is returned if there is no value. - * @throws Exception execution failed - */ - public function queryScalar($params = array()) - { - $result = $this->queryInternal('fetchColumn', $params, 0); - 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 method is best used when only the first column of result (i.e. the first element in each row) - * is needed for a query. - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @return array the first column of the query result. Empty array is returned if the query results in nothing. - * @throws Exception execution failed - */ - public function queryColumn($params = array()) - { - return $this->queryInternal('fetchAll', $params, \PDO::FETCH_COLUMN); - } - - /** - * Performs the actual DB query of a SQL statement. - * @param string $method method of PDOStatement to be called - * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative - * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]] - * or [[bindValue()]] will be ignored. - * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) - * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. - * @return mixed the method execution result - */ - private function queryInternal($method, $params, $fetchMode = null) - { - $db = $this->connection; - $sql = $db->expandTablePrefix($this->getSql()); - $this->_params = array_merge($this->_params, $params); - if ($this->_params === array()) { - $paramLog = ''; - } else { - $paramLog = "\nParameters: " . var_export($this->_params, true); - } - - \Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__); -echo $sql."\n\n"; - if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') { - $cache = \Yii::$application->getComponent($db->queryCacheID); - } - - if (isset($cache)) { - $db->queryCachingCount--; - $cacheKey = __CLASS__ . "/{$db->dsn}/{$db->username}/$sql/$paramLog"; - if (($result = $cache->get($cacheKey)) !== false) { - \Yii::trace('Query result found in cache', __CLASS__); - return $result; - } - } - - try { - if ($db->enableProfiling) { - \Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__); - } - - $this->prepare(); - if ($params === array()) { - $this->pdoStatement->execute(); - } else { - $this->pdoStatement->execute($params); - } - - if ($method === '') { - $result = new DataReader($this); - } else { - if ($fetchMode === null) { - $fetchMode = $this->fetchMode; - } - $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode); - $this->pdoStatement->closeCursor(); - } - - if ($db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - - if (isset($cache)) { - $cache->set($cacheKey, $result, $db->queryCachingDuration, $db->queryCachingDependency); - \Yii::trace('Saved query result in cache', __CLASS__); - } - - return $result; - } catch (\Exception $e) { - if ($db->enableProfiling) { - \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); - } - $message = $e->getMessage(); - \Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__); - $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; - throw new Exception($message, (int)$e->getCode(), $errorInfo); - } - } -} diff --git a/framework/db/dao/Connection.php b/framework/db/dao/Connection.php deleted file mode 100644 index 6bb1576..0000000 --- a/framework/db/dao/Connection.php +++ /dev/null @@ -1,623 +0,0 @@ - $dsn, - * 'username' => $username, - * 'password' => $password, - * )); - * $connection->active = true; // same as: $connection->open(); - * ~~~ - * - * After the DB connection is established, one can execute SQL statements like the following: - * - * ~~~ - * $command = $connection->createCommand('SELECT * FROM tbl_post'); - * $posts = $command->queryAll(); - * $command = $connection->createCommand('UPDATE tbl_post SET status=1'); - * $command->execute(); - * ~~~ - * - * One can also do prepared SQL execution and bind parameters to the prepared SQL. - * When the parameters are coming from user input, you should use this approach - * to prevent SQL injection attacks. The following is an example: - * - * ~~~ - * $command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=:id'); - * $command->bindValue(':id', $_GET['id']); - * $post = $command->query(); - * ~~~ - * - * For more information about how to perform various DB queries, please refer to [[Command]]. - * - * If the underlying DBMS supports transactions, you can perform transactional SQL queries - * like the following: - * - * ~~~ - * $transaction = $connection->beginTransaction(); - * try { - * $connection->createCommand($sql1)->execute(); - * $connection->createCommand($sql2)->execute(); - * // ... executing other SQL statements ... - * $transaction->commit(); - * } catch(Exception $e) { - * $transaction->rollBack(); - * } - * ~~~ - * - * Connection is often used as an [[\yii\base\ApplicationComponent|application component]] and configured in the application - * configuration like the following: - * - * ~~~ - * array( - * 'components' => array( - * 'db' => array( - * 'class' => '\yii\db\dao\Connection', - * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', - * 'username' => 'root', - * 'password' => '', - * 'charset' => 'utf8', - * ), - * ), - * ) - * ~~~ - * - * @property boolean $active Whether the DB connection is established. - * @property Transaction $currentTransaction The currently active transaction. Null if no active transaction. - * @property Driver $driver The database driver for the current connection. - * @property QueryBuilder $queryBuilder The query builder. - * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the sequence object. - * @property string $driverName Name of the DB driver currently being used. - * @property string $clientVersion The version information of the DB driver. - * @property array $stats The statistical results of SQL executions. - * - * @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. - * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on - * the format of the DSN string. - * @see charset - */ - 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 array PDO attributes (name=>value) that should be set when calling [[open]] - * to establish a DB connection. Please refer to the - * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for - * details about available attributes. - */ - public $attributes; - /** - * @var \PDO the PHP PDO instance associated with this DB connection. - * This property is mainly managed by [[open]] and [[close]] methods. - * When a DB connection is active, this property will represent a PDO instance; - * otherwise, it will be null. - */ - public $pdo; - /** - * @var integer number of seconds that table metadata can remain valid in cache. - * Defaults to -1, meaning schema caching is disabled. - * Use 0 to indicate that the cached data will never expire. - * - * Note that in order to enable schema caching, a valid cache component as specified - * by [[schemaCacheID]] must be enabled. - * @see schemaCachingExclude - * @see schemaCacheID - */ - public $schemaCachingDuration = -1; - /** - * @var array list of tables whose metadata should NOT be cached. Defaults to empty array. - * The table names may contain schema prefix, if any. Do not quote the table names. - * @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'. - * @see schemaCachingDuration - */ - public $schemaCacheID = 'cache'; - /** - * @var integer number of seconds that query results can remain valid in cache. - * Defaults to -1, meaning query caching is disabled. - * Use 0 to indicate that the cached data will never expire. - * - * Note that in order to enable query caching, a valid cache component as specified - * by [[queryCacheID]] must be enabled. - * - * The method [[cache()]] is provided as a convenient way of setting this property - * and [[queryCachingDependency]] on the fly. - * - * @see cache - * @see queryCachingDependency - * @see queryCacheID - */ - public $queryCachingDuration = -1; - /** - * @var \yii\caching\Dependency the dependency that will be used when saving query results into cache. - * Defaults to null, meaning no dependency. - * @see queryCachingDuration - */ - public $queryCachingDependency; - /** - * @var integer the number of SQL statements that need to be cached when they are executed next. - * Defaults to 0, meaning the query result of the next SQL statement will NOT 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. - * @see queryCachingDuration - */ - public $queryCachingCount = 0; - /** - * @var string the ID of the cache application component that is used for query caching. - * Defaults to 'cache'. - * @see queryCachingDuration - */ - public $queryCacheID = 'cache'; - /** - * @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. - * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed. - */ - public $emulatePrepare; - /** - * @var boolean whether to enable profiling for 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. - * @see getStats - */ - public $enableProfiling = false; - /** - * @var string the default prefix for table names. Defaults to null, meaning not using table prefix. - * By setting this property, any token like '{{TableName}}' in [[Command::sql]] will - * be replaced with 'prefixTableName', where 'prefix' refers to this property value. - * For example, '{{post}}' becomes 'tbl_post', if 'tbl_' is set as the table prefix. - * - * Note that if you set this property to be an empty string, then '{{post}}' will be replaced - * with 'post'. - */ - public $tablePrefix; - /** - * @var array a list of SQL statements that should be executed right after the DB connection is established. - */ - public $initSQLs; - /** - * @var array mapping between PDO driver names and [[Driver]] classes. - * The keys of the array are PDO driver names while the values the corresponding - * driver class name or configuration. Please refer to [[\Yii::createObject]] for - * details on how to specify a configuration. - * - * This property is mainly used by [[getDriver()]] when fetching the database schema information. - * You normally do not need to set this property unless you want to use your own - * [[Driver]] class to support DBMS that is not supported by Yii. - */ - public $driverMap = array( - 'pgsql' => 'yii\db\dao\pgsql\Driver', // PostgreSQL - 'mysqli' => 'yii\db\dao\mysql\Driver', // MySQL - 'mysql' => 'yii\db\dao\mysql\Driver', // MySQL - 'sqlite' => 'yii\db\dao\sqlite\Driver', // sqlite 3 - 'sqlite2' => 'yii\db\dao\sqlite\Driver', // sqlite 2 - 'mssql' => 'yi\db\dao\mssql\Driver', // Mssql driver on windows hosts - 'dblib' => 'yii\db\dao\mssql\Driver', // dblib drivers on linux (and maybe others os) hosts - 'sqlsrv' => 'yii\db\dao\mssql\Driver', // Mssql - 'oci' => 'yii\db\dao\oci\Driver', // Oracle driver - ); - /** - * @var Transaction the currently active transaction - */ - private $_transaction; - /** - * @var Driver the database driver - */ - private $_driver; - - /** - * Closes the connection when this component is being serialized. - * @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(); - } - - /** - * Returns a value indicating whether the DB connection is established. - * @return boolean whether the DB connection is established - */ - public function getActive() - { - return $this->pdo !== null; - } - - /** - * Opens or closes the DB connection. - * @param boolean $value whether to open or close the DB connection - * @throws Exception if there is any error when establishing the connection - */ - public function setActive($value) - { - $value ? $this->open() : $this->close(); - } - - /** - * Sets the parameters about query caching. - * This method is provided as a shortcut to setting three properties that are related - * with query caching: [[queryCachingDuration]], [[queryCachingDependency]] and - * [[queryCachingCount]]. - * @param integer $duration the number of seconds that query results may remain valid in cache. - * See [[queryCachingDuration]] for more details. - * @param \yii\caching\Dependency $dependency the dependency for the cached query result. - * See [[queryCachingDependency]] for more details. - * @param integer $queryCount the number of SQL queries that need to be cached after calling this method. - * See [[queryCachingCount]] for more details. - * @return Connection the connection instance itself. - */ - public function cache($duration = 300, $dependency = null, $queryCount = 1) - { - $this->queryCachingDuration = $duration; - $this->queryCachingDependency = $dependency; - $this->queryCachingCount = $queryCount; - return $this; - } - - /** - * Establishes a DB connection. - * It does nothing if a DB connection has already been established. - * @throws Exception if connection fails - */ - public function open() - { - if ($this->pdo === null) { - if (empty($this->dsn)) { - throw new Exception('Connection.dsn cannot be empty.'); - } - try { - \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); - $this->pdo = $this->createPdoInstance(); - $this->initConnection($this->pdo); - } - catch (\PDOException $e) { - \Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__); - $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; - throw new Exception($message, (int)$e->getCode(), $e->errorInfo); - } - } - } - - /** - * Closes the currently active DB connection. - * It does nothing if the connection is already closed. - */ - public function close() - { - if ($this->pdo !== null) { - \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); - $this->pdo = null; - $this->_driver = null; - $this->_transaction = null; - } - } - - /** - * Creates the PDO instance. - * This method is called by [[open]] to establish a DB connection. - * The default implementation will create a PHP PDO instance. - * You may override this method if the default PDO needs to be adapted for certain DBMS. - * @return \PDO the pdo instance - */ - protected function createPdoInstance() - { - $pdoClass = '\PDO'; - if (($pos = strpos($this->dsn, ':')) !== false) { - $driver = strtolower(substr($this->dsn, 0, $pos)); - if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { - $pdoClass = 'mssql\PDO'; - } - } - return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); - } - - /** - * Initializes the DB connection. - * This method is invoked right after the DB connection is established. - * The default implementation sets the database [[charset]] and executes SQLs specified - * in [[initSQLs]]. - */ - protected function initConnection() - { - $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if ($this->emulatePrepare !== null && constant('\PDO::ATTR_EMULATE_PREPARES')) { - $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); - } - if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli'))) { - $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); - } - if (!empty($this->initSQLs)) { - foreach ($this->initSQLs as $sql) { - $this->pdo->exec($sql); - } - } - } - - /** - * Creates a command for execution. - * @param string $sql the SQL statement to be executed - * @param array $params the parameters to be bound to the SQL statement - * @return Command the DB command - */ - public function createCommand($sql = null, $params = array()) - { - $this->open(); - return new Command($this, $sql, $params); - } - - /** - * Returns the currently active transaction. - * @return Transaction the currently active transaction. Null if no active transaction. - */ - public function getCurrentTransaction() - { - if ($this->_transaction !== null && $this->_transaction->active) { - return $this->_transaction; - } else { - return null; - } - } - - /** - * Starts a transaction. - * @return Transaction the transaction initiated - */ - public function beginTransaction() - { - \Yii::trace('Starting transaction', __CLASS__); - $this->open(); - $this->pdo->beginTransaction(); - return $this->_transaction = new Transaction($this); - } - - /** - * Returns the metadata information for the underlying database. - * @return Driver the metadata information for the underlying database. - */ - public function getDriver() - { - if ($this->_driver !== null) { - return $this->_driver; - } else { - $driver = $this->getDriverName(); - if (isset($this->driverMap[$driver])) { - return $this->_driver = \Yii::createObject($this->driverMap[$driver], $this); - } else { - throw new Exception("Connection does not support reading metadata for '$driver' database."); - } - } - } - - /** - * Returns the query builder for the current DB connection. - * @return QueryBuilder the query builder for the current DB connection. - */ - public function getQueryBuilder() - { - return $this->getDriver()->getQueryBuilder(); - } - - /** - * Obtains the metadata for the named table. - * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. - * @param boolean $refresh whether to reload the table schema even if it is found in the cache. - * @return TableSchema table metadata. Null if the named table does not exist. - */ - public function getTableSchema($name, $refresh = false) - { - return $this->getDriver()->getTableSchema($name, $refresh); - } - - /** - * 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->open(); - return $this->pdo->lastInsertId($sequenceName); - } - - /** - * Quotes a string value for use in a query. - * Note that if the parameter is not a string, it will be returned without change. - * @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_string($str)) { - return $str; - } - - $this->open(); - 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 - * @param boolean $simple if this is true, then the method will assume $name is a table name without schema prefix. - * @return string the properly quoted table name - */ - public function quoteTableName($name, $simple = false) - { - return $simple ? $this->getDriver()->quoteSimpleTableName($name) : $this->getDriver()->quoteTableName($name); - } - - /** - * Quotes a column name for use in a query. - * If the column name contains table prefix, the prefix will also be properly quoted. - * @param string $name column name - * @param boolean $simple if this is true, then the method will assume $name is a column name without table prefix. - * @return string the properly quoted column name - */ - public function quoteColumnName($name, $simple = false) - { - return $simple ? $this->getDriver()->quoteSimpleColumnName($name) : $this->getDriver()->quoteColumnName($name); - } - - /** - * Prefixes table names in a SQL statement with [[tablePrefix]]. - * By calling this method, tokens like '{{TableName}}' in the given SQL statement will - * be replaced with 'prefixTableName', where 'prefix' refers to [[tablePrefix]]. - * Note that if [[tablePrefix]] is null, this method will do nothing. - * @param string $sql the SQL statement whose table names need to be prefixed with [[tablePrefix]]. - * @return string the expanded SQL statement - * @see tablePrefix - */ - public function expandTablePrefix($sql) - { - if ($this->tablePrefix !== null && strpos($sql, '{{') !== false) { - return preg_replace('/{{(.*?)}}/', $this->tablePrefix . '\1', $sql); - } else { - return $sql; - } - } - - /** - * Determines the PDO type for the give PHP data type. - * @param string $type The PHP type (obtained by `gettype()` call). - * @return integer the corresponding PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - public function getPdoType($type) - { - static $typeMap = array( - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'NULL' => \PDO::PARAM_NULL, - ); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** - * Returns the name of the DB driver for the current [[dsn]]. - * @return string name of the DB driver - */ - public function getDriverName() - { - if (($pos = strpos($this->dsn, ':')) !== false) { - return strtolower(substr($this->dsn, 0, $pos)); - } else { - return strtolower($this->getAttribute(\PDO::ATTR_DRIVER_NAME)); - } - } - - /** - * 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->open(); - 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) - { - $this->open(); - $this->pdo->setAttribute($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, [[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. - * @see \yii\logging\Logger::getProfiling() - */ - public function getStats() - { - $logger = \Yii::getLogger(); - $timings = $logger->getProfiling(array('yii\db\dao\Command::query', 'yii\db\dao\Command::execute')); - $count = count($timings); - $time = 0; - foreach ($timings as $timing) { - $time += $timing[1]; - } - return array($count, $time); - } -} diff --git a/framework/db/dao/DataReader.php b/framework/db/dao/DataReader.php deleted file mode 100644 index 3d744f4..0000000 --- a/framework/db/dao/DataReader.php +++ /dev/null @@ -1,256 +0,0 @@ - - * @since 2.0 - */ -class DataReader extends \yii\base\Object implements \Iterator, \Countable -{ - /** - * @var \PDOStatement the PDOStatement associated with the command - */ - private $_statement; - private $_closed = false; - private $_row; - private $_index = -1; - - /** - * Constructor. - * @param Command $command the command generating the query result - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct(Command $command, $config = array()) - { - $this->_statement = $command->pdoStatement; - $this->_statement->setFetchMode(\PDO::FETCH_ASSOC); - parent::__construct($config); - } - - /** - * 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 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 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 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 attempts 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 Exception if this method is invoked twice - */ - public function rewind() - { - if ($this->_index < 0) { - $this->_row = $this->_statement->fetch(); - $this->_index = 0; - } else { - throw new Exception('DataReader 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/Driver.php b/framework/db/dao/Driver.php deleted file mode 100644 index 46a0d62..0000000 --- a/framework/db/dao/Driver.php +++ /dev/null @@ -1,283 +0,0 @@ - - * @since 2.0 - */ -abstract class Driver extends \yii\base\Object -{ - /** - * The followings are the supported abstract column data types. - */ - const TYPE_PK = 'pk'; - const TYPE_STRING = 'string'; - const TYPE_TEXT = 'text'; - const TYPE_SMALLINT = 'smallint'; - const TYPE_INTEGER = 'integer'; - const TYPE_BIGINT = 'bigint'; - const TYPE_FLOAT = 'float'; - const TYPE_DECIMAL = 'decimal'; - const TYPE_DATETIME = 'datetime'; - const TYPE_TIMESTAMP = 'timestamp'; - const TYPE_TIME = 'time'; - const TYPE_DATE = 'date'; - const TYPE_BINARY = 'binary'; - const TYPE_BOOLEAN = 'boolean'; - const TYPE_MONEY = 'money'; - - /** - * @var Connection the database connection - */ - public $connection; - /** - * @var array list of ALL table names in the database - */ - private $_tableNames = array(); - /** - * @var array list of loaded table metadata (table name => TableSchema) - */ - private $_tables = array(); - /** - * @var QueryBuilder the query builder for this database - */ - private $_builder; - - /** - * Loads the metadata for the specified table. - * @param string $name table name - * @return TableSchema DBMS-dependent table metadata, null if the table does not exist. - */ - abstract protected function loadTableSchema($name); - - /** - * Constructor. - * @param Connection $connection database connection. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($connection, $config = array()) - { - $this->connection = $connection; - parent::__construct($config); - } - - /** - * Obtains the metadata for the named table. - * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. - * @param boolean $refresh whether to reload the table schema even if it is found in the cache. - * @return TableSchema table metadata. Null if the named table does not exist. - */ - public function getTableSchema($name, $refresh = false) - { - if (isset($this->_tables[$name]) && !$refresh) { - return $this->_tables[$name]; - } - - $db = $this->connection; - - $realName = $db->expandTablePrefix($name); - - // temporarily disable query caching - if ($db->queryCachingDuration >= 0) { - $qcDuration = $db->queryCachingDuration; - $db->queryCachingDuration = -1; - } - - if (!in_array($name, $db->schemaCachingExclude, true) && $db->schemaCachingDuration >= 0 && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null) { - $key = $this->getCacheKey($name); - if ($refresh || ($table = $cache->get($key)) === false) { - $table = $this->loadTableSchema($realName); - if ($table !== null) { - $cache->set($key, $table, $db->schemaCachingDuration); - } - } - $this->_tables[$name] = $table; - } else { - $this->_tables[$name] = $table = $this->loadTableSchema($realName); - } - - if (isset($qcDuration)) { // re-enable query caching - $db->queryCachingDuration = $qcDuration; - } - - return $table; - } - - /** - * Returns the cache key for the specified table name. - * @param string $name the table name - * @return string the cache key - */ - public function getCacheKey($name) - { - return __CLASS__ . "/{$this->connection->dsn}/{$this->connection->username}/{$name}"; - } - - /** - * Returns the metadata for all tables in the database. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array the metadata for all tables in the database. - * Each array element is an instance of [[TableSchema]] (or its child class). - */ - public function getTableSchemas($schema = '') - { - $tables = array(); - foreach ($this->getTableNames($schema) as $name) { - if (($table = $this->getTableSchema($name)) !== null) { - $tables[] = $table; - } - } - return $tables; - } - - /** - * Returns all table names in the database. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * If not empty, the returned table names will be prefixed with the schema name. - * @param boolean $refresh whether to fetch the latest available table names. If this is false, - * table names fetched previously (if available) will be returned. - * @return array all table names in the database. - */ - public function getTableNames($schema = '', $refresh = false) - { - if (!isset($this->_tableNames[$schema]) || $refresh) { - $this->_tableNames[$schema] = $this->findTableNames($schema); - } - return $this->_tableNames[$schema]; - } - - /** - * @return QueryBuilder the query builder for this connection. - */ - public function getQueryBuilder() - { - if ($this->_builder === null) { - $this->_builder = $this->createQueryBuilder(); - } - return $this->_builder; - } - - /** - * Refreshes the schema. - * This method cleans up the cached table schema and names - * so that they can be recreated to reflect the database schema change. - * @param string $tableName the name of the table that needs to be refreshed. - * If null, all currently loaded tables will be refreshed. - */ - public function refresh($tableName = null) - { - $db = $this->connection; - if ($db->schemaCachingDuration >= 0 && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null) { - if ($tableName === null) { - foreach ($this->_tables as $name => $table) { - $cache->delete($this->getCacheKey($name)); - } - $this->_tables = array(); - } else { - $cache->delete($this->getCacheKey($tableName)); - unset($this->_tables[$tableName]); - } - } - } - - /** - * 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 - * @see quoteSimpleTableName - */ - public function quoteTableName($name) - { - if (strpos($name, '.') === false) { - return $this->quoteSimpleTableName($name); - } - $parts = explode('.', $name); - foreach ($parts as $i => $part) { - $parts[$i] = $this->quoteSimpleTableName($part); - } - return implode('.', $parts); - - } - - /** - * Quotes a simple table name for use in a query. - * A simple table name does not schema prefix. - * @param string $name table name - * @return string the properly quoted table name - */ - public function quoteSimpleTableName($name) - { - return strpos($name, "'") !== false ? $name : "'" . $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 - * @see quoteSimpleColumnName - */ - public function quoteColumnName($name) - { - if (($pos = strrpos($name, '.')) !== false) { - $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.'; - $name = substr($name, $pos + 1); - } else { - $prefix = ''; - } - return $prefix . $this->quoteSimpleColumnName($name); - } - - /** - * Quotes a simple column name for use in a query. - * A simple column name does not contain prefix. - * @param string $name column name - * @return string the properly quoted column name - */ - public function quoteSimpleColumnName($name) - { - return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; - } - - /** - * Creates a query builder for the database. - * This method may be overridden by child classes to create a DBMS-specific query builder. - * @return QueryBuilder query builder instance - */ - public function createQueryBuilder() - { - return new QueryBuilder($this->connection); - } - - /** - * Returns all table names in the database. - * This method should be overridden by child classes in order to support this feature - * because the default implementation simply throws an exception. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * If not empty, the returned table names will be prefixed with the schema name. - * @return array all table names in the database. - */ - protected function findTableNames($schema = '') - { - throw new Exception(get_class($this) . 'does not support fetching all table names.'); - } -} diff --git a/framework/db/dao/Expression.php b/framework/db/dao/Expression.php deleted file mode 100644 index 5975997..0000000 --- a/framework/db/dao/Expression.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @since 2.0 - */ -class Expression extends \yii\base\Object -{ - /** - * @var string the DB expression - */ - public $expression; - /** - * @var array list of parameters that should be bound for this expression. - * The keys are placeholders appearing in [[expression]] and the values - * are the corresponding parameter values. - */ - public $params = array(); - - /** - * Constructor. - * @param string $expression the DB expression - * @param array $params parameters - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($expression, $params = array(), $config = array()) - { - $this->expression = $expression; - $this->params = $params; - parent::__construct($config); - } - - /** - * String magic method - * @return string the DB expression - */ - public function __toString() - { - return $this->expression; - } -} \ No newline at end of file diff --git a/framework/db/dao/Query.php b/framework/db/dao/Query.php deleted file mode 100644 index 0769f92..0000000 --- a/framework/db/dao/Query.php +++ /dev/null @@ -1,310 +0,0 @@ -select('id, name') - * ->from('tbl_user') - * ->limit(10); - * // get the actual SQL statement - * echo $query->getSql(); - * // or execute the query - * $users = $query->createCommand()->queryAll(); - * ~~~ - * - * By calling [[getSql()]], we can obtain the actual SQL statement from a Query object. - * And by calling [[createCommand()]], we can get a [[Command]] instance which can be further - * used to perform/execute the DB query against a database. - * - * @property string $sql the SQL statement represented by this query object. - * - * @author Qiang Xue - * @since 2.0 - */ -class Query extends BaseQuery -{ - /** - * @var array the operation that this query represents. This refers to the method call as well as - * the corresponding parameters for constructing a non-select SQL statement (e.g. INSERT, CREATE TABLE). - * This property is mainly maintained by methods such as [[insert()]], [[update()]], [[createTable()]]. - * If this property is not set, it means this query represents a SELECT statement. - */ - public $operation; - /** - * @var string the SQL statement that this query represents. This is directly set by user. - */ - public $sql; - - /** - * Generates and returns the SQL statement according to this query. - * Note that after calling this method, [[params]] may be modified with additional - * parameters generated by the query builder. - * @param Connection $connection the database connection used to generate the SQL statement. - * If this parameter is not given, the `db` application component will be used. - * @return string the generated SQL statement - */ - public function getSql($connection = null) - { - if ($this->sql !== null) { - return $this->sql; - } - if ($connection === null) { - $connection = \Yii::$application->db; - } - $qb = $connection->getQueryBuilder(); - if ($this->operation !== null) { - $params = $this->operation; - $method = array_shift($params); - $qb->query = $this; - return call_user_func_array(array($qb, $method), $params); - } else { - /** @var $qb QueryBuilder */ - return $qb->build($this); - } - } - - /** - * Creates a DB command that can be used to execute this query. - * @param Connection $connection the database connection used to generate the SQL statement. - * If this parameter is not given, the `db` application component will be used. - * @return Command the created DB command instance. - */ - public function createCommand($connection = null) - { - if ($connection === null) { - $connection = \Yii::$application->db; - } - $sql = $this->getSql($connection); - return $connection->createCommand($sql, $this->params); - } - - /** - * 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 Query the query object itself - */ - public function insert($table, $columns) - { - $this->operation = array(__FUNCTION__, $table, $columns); - return $this; - } - - /** - * Creates and executes an UPDATE SQL statement. - * The method will properly escape the column names and bind the values to be updated. - * @param string $table the table to be updated. - * @param array $columns the column data (name=>value) to be updated. - * @param string|array $condition the conditions that will be put in the WHERE part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return Query the query object itself - */ - public function update($table, $columns, $condition = '', $params = array()) - { - $this->operation = array(__FUNCTION__, $table, $columns, $condition, $params); - return $this; - } - - /** - * Creates and executes a DELETE SQL statement. - * @param string $table the table where the data will be deleted from. - * @param string|array $condition the conditions that will be put in the WHERE part. - * Please refer to [[where()]] on how to specify this parameter. - * @param array $params the parameters (name=>value) to be bound to the query. - * @return Query the query object itself - */ - public function delete($table, $condition = '', $params = array()) - { - $this->operation = array(__FUNCTION__, $table, $condition, $params); - return $this; - } - - /** - * Builds and executes a SQL statement for creating a new DB table. - * - * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), - * where name stands for a column name which will be properly quoted by the method, and definition - * stands for the column type which can contain an abstract DB type. - * The method [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called - * to convert the abstract column types to physical ones. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * - * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly - * inserted into the generated SQL. - * - * @param string $table the name of the table to be created. The name will be properly quoted by the method. - * @param array $columns the columns (name=>definition) in the new table. - * @param string $options additional SQL fragment that will be appended to the generated SQL. - * @return Query the query object itself - */ - public function createTable($table, $columns, $options = null) - { - $this->operation = array(__FUNCTION__, $table, $columns, $options); - return $this; - } - - /** - * Builds and executes a SQL statement for renaming a DB table. - * @param string $table the table to be renamed. The name will be properly quoted by the method. - * @param string $newName the new table name. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function renameTable($table, $newName) - { - $this->operation = array(__FUNCTION__, $table, $newName); - return $this; - } - - /** - * Builds and executes a SQL statement for dropping a DB table. - * @param string $table the table to be dropped. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function dropTable($table) - { - $this->operation = array(__FUNCTION__, $table); - return $this; - } - - /** - * Builds and executes a SQL statement for truncating a DB table. - * @param string $table the table to be truncated. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function truncateTable($table) - { - $this->operation = array(__FUNCTION__, $table); - return $this; - } - - /** - * Builds and executes a SQL statement for adding a new DB column. - * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. - * @param string $column the name of the new column. The name will be properly quoted by the method. - * @param string $type the column type. [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called - * to convert the give column type to the physical one. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Query the query object itself - */ - public function addColumn($table, $column, $type) - { - $this->operation = array(__FUNCTION__, $table, $column, $type); - return $this; - } - - /** - * Builds and executes a SQL statement for dropping a DB column. - * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. - * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function dropColumn($table, $column) - { - $this->operation = array(__FUNCTION__, $table, $column); - return $this; - } - - /** - * Builds and executes a SQL statement for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $oldName the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function renameColumn($table, $oldName, $newName) - { - $this->operation = array(__FUNCTION__, $table, $oldName, $newName); - return $this; - } - - /** - * Builds and executes a SQL statement for changing the definition of a column. - * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. - * @param string $column the name of the column to be changed. The name will be properly quoted by the method. - * @param string $type the column type. [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called - * to convert the give column type to the physical one. For example, `string` will be converted - * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. - * @return Query the query object itself - */ - public function alterColumn($table, $column, $type) - { - $this->operation = array(__FUNCTION__, $table, $column, $type); - return $this; - } - - /** - * Builds a SQL statement for adding a foreign key constraint to an existing table. - * The method will properly quote the table and column names. - * @param string $name the name of the foreign key constraint. - * @param string $table the table that the foreign key constraint will be added to. - * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. - * @param string $refTable the table that the foreign key references to. - * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. - * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL - * @return Query the query object itself - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - $this->operation = array(__FUNCTION__, $name, $table, $columns, $refTable, $refColumns, $delete, $update); - return $this; - } - - /** - * Builds a SQL statement for dropping a foreign key constraint. - * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function dropForeignKey($name, $table) - { - $this->operation = array(__FUNCTION__, $name, $table); - return $this; - } - - /** - * Builds and executes a SQL statement for creating a new index. - * @param string $name the name of the index. The name will be properly quoted by the method. - * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. - * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them - * by commas. The column names will be properly quoted by the method. - * @param boolean $unique whether to add UNIQUE constraint on the created index. - * @return Query the query object itself - */ - public function createIndex($name, $table, $columns, $unique = false) - { - $this->operation = array(__FUNCTION__, $name, $table, $columns, $unique); - return $this; - } - - /** - * Builds and executes a SQL statement for dropping an index. - * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. - * @return Query the query object itself - */ - public function dropIndex($name, $table) - { - $this->operation = array(__FUNCTION__, $name, $table); - return $this; - } -} diff --git a/framework/db/dao/QueryBuilder.php b/framework/db/dao/QueryBuilder.php deleted file mode 100644 index 5a76729..0000000 --- a/framework/db/dao/QueryBuilder.php +++ /dev/null @@ -1,947 +0,0 @@ - - * @since 2.0 - */ -class QueryBuilder extends \yii\base\Object -{ - /** - * @var Connection the database connection. - */ - public $connection; - /** - * @var string the separator between different fragments of a SQL statement. - * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. - */ - public $separator = " "; - /** - * @var boolean whether to automatically quote table and column names when generating SQL statements. - */ - public $autoQuote = true; - /** - * @var array the abstract column types mapped to physical column types. - * This is mainly used to support creating/modifying tables using DB-independent data type specifications. - * Child classes should override this property to declare supported type mappings. - */ - public $typeMap = array(); - /** - * @var Query the Query object that is currently being processed by the query builder to generate a SQL statement. - */ - public $query; - - /** - * Constructor. - * @param Connection $connection the database connection. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($connection, $config = array()) - { - $this->connection = $connection; - parent::__construct($config); - } - - /** - * Generates a SELECT SQL statement from a [[BaseQuery]] object. - * @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated - * @return string the generated SQL statement - */ - public function build($query) - { - $clauses = array( - $this->buildSelect($query->select, $query->distinct, $query->selectOption), - $this->buildFrom($query->from), - $this->buildJoin($query->join), - $this->buildWhere($query->where), - $this->buildGroup($query->groupBy), - $this->buildHaving($query->having), - $this->buildUnion($query->union), - $this->buildOrder($query->orderBy), - $this->buildLimit($query->limit, $query->offset), - ); - return implode($this->separator, array_filter($clauses)); - } - - /** - * Creates and executes an INSERT SQL statement. - * The method will properly escape the column names, and bind the values to be inserted. - * For example, - * - * ~~~ - * $sql = $queryBuilder->insert('tbl_user', array( - * 'name' => 'Sam', - * 'age' => 30, - * )); - * ~~~ - * - * @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 string the INSERT SQL - */ - public function insert($table, $columns) - { - $names = array(); - $placeholders = array(); - $count = 0; - $params = array(); - foreach ($columns as $name => $value) { - $names[] = $this->quoteColumnName($name); - if ($value instanceof Expression) { - $placeholders[] = $value->expression; - foreach ($value->params as $n => $v) { - $params[$n] = $v; - } - } else { - $placeholders[] = ':p' . $count; - $params[':p' . $count] = $value; - $count++; - } - } - if ($this->query instanceof BaseQuery) { - $this->query->addParams($params); - } - - return 'INSERT INTO ' . $this->quoteTableName($table) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; - } - - /** - * Creates and executes an UPDATE SQL statement. - * The method will properly escape the column names and bind the values to be updated. - * For example, - * - * ~~~ - * $params = array(); - * $sql = $queryBuilder->update('tbl_user', array( - * 'status' => 1, - * ), 'age > 30', $params); - * ~~~ - * - * @param string $table the table to be updated. - * @param array $columns the column data (name=>value) to be updated. - * @param mixed $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the query. - * @return string the UPDATE SQL - */ - public function update($table, $columns, $condition = '', $params = array()) - { - $lines = array(); - $count = 0; - foreach ($columns as $name => $value) { - if ($value instanceof Expression) { - $lines[] = $this->quoteColumnName($name, true) . '=' . $value->expression; - foreach ($value->params as $n => $v) { - $params[$n] = $v; - } - } else { - $lines[] = $this->quoteColumnName($name, true) . '=:p' . $count; - $params[':p' . $count] = $value; - $count++; - } - } - if ($this->query instanceof BaseQuery) { - $this->query->addParams($params); - } - $sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines); - if (($where = $this->buildCondition($condition)) !== '') { - $sql .= ' WHERE ' . $where; - } - - return $sql; - } - - /** - * Creates and executes a DELETE SQL statement. - * For example, - * - * ~~~ - * $sql = $queryBuilder->delete('tbl_user', 'status = 0'); - * ~~~ - * - * @param string $table the table where the data will be deleted from. - * @param mixed $condition the condition that will be put in the WHERE part. Please - * refer to [[Query::where()]] on how to specify condition. - * @param array $params the parameters to be bound to the query. - * @return string the DELETE SQL - */ - public function delete($table, $condition = '', $params = array()) - { - $sql = 'DELETE FROM ' . $this->quoteTableName($table); - if (($where = $this->buildCondition($condition)) !== '') { - $sql .= ' WHERE ' . $where; - } - if ($params !== array() && $this->query instanceof BaseQuery) { - $this->query->addParams($params); - } - return $sql; - } - - /** - * Builds 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 [[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. - * - * For example, - * - * ~~~ - * $sql = $queryBuilder->createTable('tbl_user', array( - * 'id' => 'pk', - * 'name' => 'string', - * 'age' => 'integer', - * )); - * ~~~ - * - * @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 string the SQL statement for creating a new DB table. - */ - public function createTable($table, $columns, $options = null) - { - $cols = array(); - foreach ($columns as $name => $type) { - if (is_string($name)) { - $cols[] = "\t" . $this->quoteColumnName($name) . ' ' . $this->getColumnType($type); - } else { - $cols[] = "\t" . $type; - } - } - $sql = "CREATE TABLE " . $this->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; - return $options === null ? $sql : $sql . ' ' . $options; - } - - /** - * Builds a SQL statement for renaming a DB table. - * @param string $oldName 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 string the SQL statement for renaming a DB table. - */ - public function renameTable($oldName, $newName) - { - return 'RENAME TABLE ' . $this->quoteTableName($oldName) . ' TO ' . $this->quoteTableName($newName); - } - - /** - * Builds 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 string the SQL statement for dropping a DB table. - */ - public function dropTable($table) - { - return "DROP TABLE " . $this->quoteTableName($table); - } - - /** - * Builds 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 string the SQL statement for truncating a DB table. - */ - public function truncateTable($table) - { - return "TRUNCATE TABLE " . $this->quoteTableName($table); - } - - /** - * Builds 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 [[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 string the SQL statement for adding a new column. - */ - public function addColumn($table, $column, $type) - { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD ' . $this->quoteColumnName($column) . ' ' - . $this->getColumnType($type); - } - - /** - * Builds 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 string the SQL statement for dropping a DB column. - */ - public function dropColumn($table, $column) - { - return "ALTER TABLE " . $this->quoteTableName($table) - . " DROP COLUMN " . $this->quoteColumnName($column, true); - } - - /** - * Builds a SQL statement for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $oldName the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return string the SQL statement for renaming a DB column. - */ - public function renameColumn($table, $oldName, $newName) - { - return "ALTER TABLE " . $this->quoteTableName($table) - . " RENAME COLUMN " . $this->quoteColumnName($oldName, true) - . " TO " . $this->quoteColumnName($newName, true); - } - - /** - * Builds 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 [[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 string the SQL statement for changing the definition of a column. - */ - public function alterColumn($table, $column, $type) - { - return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE ' - . $this->quoteColumnName($column, true) . ' ' - . $this->quoteColumnName($column, true) . ' ' - . $this->getColumnType($type); - } - - /** - * 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|array $columns the name of the column to that the constraint will be added on. - * If there are multiple columns, separate them with commas or use an array to represent them. - * @param string $refTable the table that the foreign key references to. - * @param string|array $refColumns the name of the column that the foreign key references to. - * If there are multiple columns, separate them with commas or use an array to represent them. - * @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 string the SQL statement for adding a foreign key constraint to an existing table. - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - $sql = 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD CONSTRAINT ' . $this->quoteColumnName($name) - . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' - . ' REFERENCES ' . $this->quoteTableName($refTable) - . ' (' . $this->buildColumns($refColumns) . ')'; - if ($delete !== null) { - $sql .= ' ON DELETE ' . $delete; - } - if ($update !== null) { - $sql .= ' ON UPDATE ' . $update; - } - return $sql; - } - - /** - * 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 string the SQL statement for dropping a foreign key constraint. - */ - public function dropForeignKey($name, $table) - { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' DROP CONSTRAINT ' . $this->quoteColumnName($name); - } - - /** - * Builds 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|array $columns the column(s) that should be included in the index. If there are multiple columns, - * separate them with commas or use an array to represent them. Each column name will be properly quoted - * by the method, unless a parenthesis is found in the name. - * @param boolean $unique whether to add UNIQUE constraint on the created index. - * @return string the SQL statement for creating a new index. - */ - public function createIndex($name, $table, $columns, $unique = false) - { - return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') - . $this->quoteTableName($name) . ' ON ' - . $this->quoteTableName($table) - . ' (' . $this->buildColumns($columns) . ')'; - } - - /** - * Builds 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 string the SQL statement for dropping an index. - */ - public function dropIndex($name, $table) - { - return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table); - } - - /** - * Resets the sequence value of a table's primary key. - * The sequence will be reset such that the primary key of the next new row inserted - * will have the specified value or 1. - * @param string $table the table schema whose primary key sequence will be reset - * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. - */ - public function resetSequence($table, $value = null) - { - } - - /** - * Enables or disables integrity check. - * @param boolean $check whether to turn on or off the integrity check. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - */ - public function checkIntegrity($check = true, $schema = '') - { - } - - /** - * Converts an abstract column type into a physical column type. - * The conversion is done using the type map specified in [[typeMap]]. - * The following abstract column types are supported (using MySQL as an example to explain the corresponding - * physical types): - * - * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" - * - `string`: string type, will be converted into "varchar(255)" - * - `text`: a long string type, will be converted into "text" - * - `smallint`: a small integer type, will be converted into "smallint(6)" - * - `integer`: integer type, will be converted into "int(11)" - * - `bigint`: a big integer type, will be converted into "bigint(20)" - * - `boolean`: boolean type, will be converted into "tinyint(1)" - * - `float``: float number type, will be converted into "float" - * - `decimal`: decimal number type, will be converted into "decimal" - * - `datetime`: datetime type, will be converted into "datetime" - * - `timestamp`: timestamp type, will be converted into "timestamp" - * - `time`: time type, will be converted into "time" - * - `date`: date type, will be converted into "date" - * - `money`: money type, will be converted into "decimal(19,4)" - * - `binary`: binary data type, will be converted into "blob" - * - * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only - * the first part will be converted, and the rest of the parts will be appended to the converted result. - * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. - * - * If a type cannot be found in [[typeMap]], it will be returned without any change. - * @param string $type abstract column type - * @return string physical column type. - */ - public function getColumnType($type) - { - if (isset($this->typeMap[$type])) { - return $this->typeMap[$type]; - } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { - if (isset($this->typeMap[$matches[0]])) { - return preg_replace('/^\w+/', $this->typeMap[$matches[0]], $type); - } - } - return $type; - } - - /** - * Parses the condition specification and generates the corresponding SQL expression. - * @param string|array $condition the condition specification. Please refer to [[BaseQuery::where()]] - * on how to specify a condition. - * @return string the generated SQL expression - * @throws \yii\db\Exception if the condition is in bad format - */ - public function buildCondition($condition) - { - static $builders = array( - 'AND' => 'buildAndCondition', - 'OR' => 'buildAndCondition', - 'BETWEEN' => 'buildBetweenCondition', - 'NOT BETWEEN' => 'buildBetweenCondition', - 'IN' => 'buildInCondition', - 'NOT IN' => 'buildInCondition', - 'LIKE' => 'buildLikeCondition', - 'NOT LIKE' => 'buildLikeCondition', - 'OR LIKE' => 'buildLikeCondition', - 'OR NOT LIKE' => 'buildLikeCondition', - ); - - if (!is_array($condition)) { - return (string)$condition; - } elseif ($condition === array()) { - return ''; - } - if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... - $operator = strtoupper($condition[0]); - if (isset($builders[$operator])) { - $method = $builders[$operator]; - array_shift($condition); - return $this->$method($operator, $condition); - } else { - throw new Exception('Found unknown operator in query: ' . $operator); - } - } else { // hash format: 'column1'=>'value1', 'column2'=>'value2', ... - return $this->buildHashCondition($condition); - } - } - - private function buildHashCondition($condition) - { - $parts = array(); - foreach ($condition as $column => $value) { - if (is_array($value)) { // IN condition - $parts[] = $this->buildInCondition('in', array($column, $value)); - } else { - if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); - } - if ($value === null) { - $parts[] = "$column IS NULL"; - } elseif (is_string($value)) { - $parts[] = "$column=" . $this->connection->quoteValue($value); - } else { - $parts[] = "$column=$value"; - } - } - } - return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; - } - - private function buildAndCondition($operator, $operands) - { - $parts = array(); - foreach ($operands as $operand) { - if (is_array($operand)) { - $operand = $this->buildCondition($operand); - } - if ($operand !== '') { - $parts[] = $operand; - } - } - if ($parts !== array()) { - return '(' . implode(") $operator (", $parts) . ')'; - } else { - return ''; - } - } - - private function buildBetweenCondition($operator, $operands) - { - if (!isset($operands[0], $operands[1], $operands[2])) { - throw new Exception("Operator '$operator' requires three operands."); - } - - list($column, $value1, $value2) = $operands; - - if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); - } - $value1 = is_string($value1) ? $this->connection->quoteValue($value1) : (string)$value1; - $value2 = is_string($value2) ? $this->connection->quoteValue($value2) : (string)$value2; - - return "$column $operator $value1 AND $value2"; - } - - private function buildInCondition($operator, $operands) - { - if (!isset($operands[0], $operands[1])) { - throw new Exception("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - $values = (array)$values; - - if ($values === array() || $column === array()) { - return $operator === 'IN' ? '0=1' : ''; - } - - if (is_array($column)) { - if (count($column) > 1) { - return $this->buildCompositeInCondition($operator, $column, $values); - } else { - $column = reset($column); - foreach ($values as $i => $value) { - if (is_array($value)) { - $value = isset($value[$column]) ? $value[$column] : null; - } - if ($value === null) { - $values[$i] = 'NULL'; - } else { - $values[$i] = is_string($value) ? $this->connection->quoteValue($value) : (string)$value; - } - } - } - } - if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); - } - - if (count($values) > 1) { - return "$column $operator (" . implode(', ', $values) . ')'; - } else { - $operator = $operator === 'IN' ? '=' : '<>'; - return "$column$operator{$values[0]}"; - } - } - - protected function buildCompositeInCondition($operator, $columns, $values) - { - foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->quoteColumnName($column); - } - } - $vss = array(); - foreach ($values as $value) { - $vs = array(); - foreach ($columns as $column) { - if (isset($value[$column])) { - $vs[] = is_string($value[$column]) ? $this->connection->quoteValue($value[$column]) : (string)$value[$column]; - } else { - $vs[] = 'NULL'; - } - } - $vss[] = '(' . implode(', ', $vs) . ')'; - } - return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; - } - - private function buildLikeCondition($operator, $operands) - { - if (!isset($operands[0], $operands[1])) { - throw new Exception("Operator '$operator' requires two operands."); - } - - list($column, $values) = $operands; - - $values = (array)$values; - - 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'; - } - - if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); - } - - $parts = array(); - foreach ($values as $value) { - $parts[] = "$column $operator " . $this->connection->quoteValue($value); - } - - return implode($andor, $parts); - } - - /** - * @param string|array $columns - * @param boolean $distinct - * @param string $selectOption - * @return string the SELECT clause built from [[query]]. - */ - public function buildSelect($columns, $distinct = false, $selectOption = null) - { - $select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; - if ($selectOption !== null) { - $select .= ' ' . $selectOption; - } - - if (empty($columns)) { - return $select . ' *'; - } - - if ($this->autoQuote) { - $driver = $this->connection->driver; - if (!is_array($columns)) { - if (strpos($columns, '(') !== false) { - return $select . ' ' . $columns; - } else { - $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+)([\w\-_\.]+)$/', $column, $matches)) { - $columns[$i] = $driver->quoteColumnName($matches[1]) . ' AS ' . $driver->quoteSimpleColumnName($matches[2]); - } else { - $columns[$i] = $driver->quoteColumnName($column); - } - } - } - } - - if (is_array($columns)) { - $columns = implode(', ', $columns); - } - - return $select . ' ' . $columns; - } - - /** - * @param string|array $tables - * @return string the FROM clause built from [[query]]. - */ - public function buildFrom($tables) - { - if (empty($tables)) { - return ''; - } - - if ($this->autoQuote) { - $driver = $this->connection->driver; - if (!is_array($tables)) { - if (strpos($tables, '(') !== false) { - return 'FROM ' . $tables; - } else { - $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+)(.*)$/i', $table, $matches)) { // with alias - $tables[$i] = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]); - } else { - $tables[$i] = $driver->quoteTableName($table); - } - } - } - } - - if (is_array($tables)) { - $tables = implode(', ', $tables); - } - - return 'FROM ' . $tables; - } - - /** - * @param string|array $joins - * @return string the JOIN clause built from [[query]]. - */ - public function buildJoin($joins) - { - if (empty($joins)) { - return ''; - } - if (is_string($joins)) { - return $joins; - } - - foreach ($joins as $i => $join) { - if (is_array($join)) { // 0:join type, 1:table name, 2:on-condition - if (isset($join[0], $join[1])) { - $table = $join[1]; - if ($this->autoQuote && strpos($table, '(') === false) { - $driver = $this->connection->driver; - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias - $table = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]); - } else { - $table = $driver->quoteTableName($table); - } - } - $joins[$i] = $join[0] . ' ' . $table; - if (isset($join[2])) { - $condition = $this->buildCondition($join[2]); - if ($condition !== '') { - $joins[$i] .= ' ON ' . $this->buildCondition($join[2]); - } - } - } else { - throw new Exception('A join clause must be specified as an array of at least two elements.'); - } - } - } - - return implode($this->separator, $joins); - } - - /** - * @param string|array $condition - * @return string the WHERE clause built from [[query]]. - */ - public function buildWhere($condition) - { - $where = $this->buildCondition($condition); - return $where === '' ? '' : 'WHERE ' . $where; - } - - /** - * @param string|array $columns - * @return string the GROUP BY clause - */ - public function buildGroup($columns) - { - if (empty($columns)) { - return ''; - } else { - return 'GROUP BY ' . $this->buildColumns($columns); - } - } - - /** - * @param string|array $condition - * @return string the HAVING clause built from [[query]]. - */ - public function buildHaving($condition) - { - $having = $this->buildCondition($condition); - return $having === '' ? '' : 'HAVING ' . $having; - } - - /** - * @param string|array $columns - * @return string the ORDER BY clause built from [[query]]. - */ - public function buildOrder($columns) - { - if (empty($columns)) { - return ''; - } - if ($this->autoQuote) { - $driver = $this->connection->driver; - if (!is_array($columns)) { - if (strpos($columns, '(') !== false) { - return 'ORDER BY ' . $columns; - } else { - $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] = $driver->quoteColumnName($matches[1]) . ' ' . $matches[2]; - } else { - $columns[$i] = $driver->quoteColumnName($column); - } - } - } - } - if (is_array($columns)) { - $columns = implode(', ', $columns); - } - return 'ORDER BY ' . $columns; - } - - /** - * @param integer $limit - * @param integer $offset - * @return string the LIMIT and OFFSET clauses built from [[query]]. - */ - public function buildLimit($limit, $offset) - { - $sql = ''; - if ($limit !== null && $limit >= 0) { - $sql = 'LIMIT ' . (int)$limit; - } - if ($offset > 0) { - $sql .= ' OFFSET ' . (int)$offset; - } - return ltrim($sql); - } - - /** - * @param string|array $unions - * @return string the UNION clause built from [[query]]. - */ - public function buildUnion($unions) - { - if (empty($unions)) { - return ''; - } - if (!is_array($unions)) { - $unions = array($unions); - } - foreach ($unions as $i => $union) { - if ($union instanceof BaseQuery) { - $unions[$i] = $this->build($union); - } - } - return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)"; - } - - /** - * Processes columns and properly quote them if necessary. - * This method will quote columns if [[autoQuote]] is true. - * It will join all columns into a string with comma as separators. - * @param string|array $columns the columns to be processed - * @return string the processing result - */ - protected function buildColumns($columns) - { - if ($this->autoQuote) { - if (!is_array($columns)) { - if (strpos($columns, '(') !== false) { - return $columns; - } else { - $columns = preg_split('/\s*,\s*/', $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->quoteColumnName($column); - } - } - } - return is_array($columns) ? implode(', ', $columns) : $columns; - } - - /** - * Quotes a table name for use in a query. - * This method will perform name quoting only when [[autoQuote]] is true. - * @param string $name table name - * @param boolean $simple whether the name should be treated as a simple table name without any prefix. - * @return string the properly quoted table name - */ - protected function quoteTableName($name, $simple = false) - { - if ($this->autoQuote) { - return $this->connection->quoteTableName($name, $simple); - } else { - return $name; - } - } - - /** - * Quotes a column name for use in a query. - * This method will perform name quoting only when [[autoQuote]] is true. - * @param string $name column name - * @param boolean $simple whether the name should be treated as a simple column name without any prefix. - * @return string the properly quoted column name - */ - protected function quoteColumnName($name, $simple = false) - { - if ($this->autoQuote) { - return $this->connection->quoteColumnName($name, $simple); - } else { - return $name; - } - } -} diff --git a/framework/db/dao/TableSchema.php b/framework/db/dao/TableSchema.php deleted file mode 100644 index b905e3b..0000000 --- a/framework/db/dao/TableSchema.php +++ /dev/null @@ -1,109 +0,0 @@ - - * @since 2.0 - */ -class TableSchema extends \yii\base\Object -{ - /** - * @var string name of the catalog (database) that this table belongs to. - * Defaults to null, meaning no catalog (or the current database). - * This property is only meaningful for MSSQL. - */ - public $catalogName; - /** - * @var string name of the schema that this table belongs to. - */ - public $schemaName; - /** - * @var string name of this table. - */ - public $name; - /** - * @var string quoted name of this table. This will include [[schemaName]] if it is not empty. - */ - public $quotedName; - /** - * @var string[] primary keys of this table. - */ - public $primaryKey = array(); - /** - * @var string sequence name for the primary key. Null if no sequence. - */ - public $sequenceName; - /** - * @var array foreign keys of this table. Each array element is of the following structure: - * - * ~~~ - * array( - * 'ForeignTableName', - * 'fk1' => 'pk1', // pk1 is in foreign table - * 'fk2' => 'pk2', // if composite foreign key - * ) - * ~~~ - */ - public $foreignKeys = array(); - /** - * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names. - */ - public $columns = array(); - - /** - * Gets the named column metadata. - * This is a convenient method for retrieving a named column even if it does not exist. - * @param string $name column name - * @return ColumnSchema metadata of the named column. Null if the named column does not exist. - */ - public function getColumn($name) - { - return isset($this->columns[$name]) ? $this->columns[$name] : null; - } - - /** - * Returns the names of all columns in this table. - * @return array list of column names - */ - public function getColumnNames() - { - return array_keys($this->columns); - } - - /** - * Manually specifies the primary key for this table. - * @param string|array $keys the primary key (can be composite) - * @throws \yii\db\Exception if the specified key cannot be found in the table. - */ - public function fixPrimaryKey($keys) - { - if (!is_array($keys)) { - $keys = array($keys); - } - $this->primaryKey = $keys; - foreach ($this->columns as $column) { - $column->isPrimaryKey = false; - } - foreach ($keys as $key) { - if (isset($this->columns[$key])) { - $this->columns[$key]->isPrimaryKey = true; - } else { - throw new Exception("Primary key '$key' cannot be found in table '{$this->name}'."); - } - } - } -} diff --git a/framework/db/dao/Transaction.php b/framework/db/dao/Transaction.php deleted file mode 100644 index 0556ecb..0000000 --- a/framework/db/dao/Transaction.php +++ /dev/null @@ -1,91 +0,0 @@ -beginTransaction(); - * try { - * $connection->createCommand($sql1)->execute(); - * $connection->createCommand($sql2)->execute(); - * //.... other SQL executions - * $transaction->commit(); - * } catch(Exception $e) { - * $transaction->rollBack(); - * } - * ~~~ - * - * @author Qiang Xue - * @since 2.0 - */ -class Transaction extends \yii\base\Object -{ - /** - * @var boolean whether this transaction is active. Only an active transaction - * can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started. - */ - public $active; - /** - * @var Connection the database connection that this transaction is associated with. - */ - public $connection; - - /** - * Constructor. - * @param Connection $connection the connection associated with this transaction - * @param array $config name-value pairs that will be used to initialize the object properties - * @see Connection::beginTransaction - */ - public function __construct($connection, $config = array()) - { - $this->active = true; - $this->connection = $connection; - parent::__construct($config); - } - - /** - * Commits a transaction. - * @throws Exception if the transaction or the DB connection is not active. - */ - public function commit() - { - if ($this->active && $this->connection->getActive()) { - \Yii::trace('Committing transaction', __CLASS__); - $this->connection->pdo->commit(); - $this->active = false; - } else { - throw new Exception('Failed to commit transaction: transaction was inactive.'); - } - } - - /** - * Rolls back a transaction. - * @throws Exception if the transaction or the DB connection is not active. - */ - public function rollback() - { - if ($this->active && $this->connection->getActive()) { - \Yii::trace('Rolling back transaction', __CLASS__); - $this->connection->pdo->rollBack(); - $this->active = false; - } else { - throw new Exception('Failed to roll back transaction: transaction was inactive.'); - } - } -} diff --git a/framework/db/dao/mysql/Driver.php b/framework/db/dao/mysql/Driver.php deleted file mode 100644 index e9c0f8d..0000000 --- a/framework/db/dao/mysql/Driver.php +++ /dev/null @@ -1,269 +0,0 @@ - - * @since 2.0 - */ -class Driver extends \yii\db\dao\Driver -{ - /** - * @var array mapping from physical column types (keys) to abstract column types (values) - */ - public $typeMap = array( - 'tinyint' => self::TYPE_SMALLINT, - 'bit' => self::TYPE_SMALLINT, - 'smallint' => self::TYPE_SMALLINT, - 'mediumint' => self::TYPE_INTEGER, - 'int' => self::TYPE_INTEGER, - 'integer' => self::TYPE_INTEGER, - 'bigint' => self::TYPE_BIGINT, - 'float' => self::TYPE_FLOAT, - 'double' => self::TYPE_FLOAT, - 'real' => self::TYPE_FLOAT, - 'decimal' => self::TYPE_DECIMAL, - 'numeric' => self::TYPE_DECIMAL, - 'tinytext' => self::TYPE_TEXT, - 'mediumtext' => self::TYPE_TEXT, - 'longtext' => self::TYPE_TEXT, - 'text' => self::TYPE_TEXT, - 'varchar' => self::TYPE_STRING, - 'string' => self::TYPE_STRING, - 'char' => self::TYPE_STRING, - 'datetime' => self::TYPE_DATETIME, - 'year' => self::TYPE_DATE, - 'date' => self::TYPE_DATE, - 'time' => self::TYPE_TIME, - 'timestamp' => self::TYPE_TIMESTAMP, - 'enum' => self::TYPE_STRING, - ); - - /** - * Quotes a table name for use in a query. - * A simple table name has no schema prefix. - * @param string $name table name - * @return string the properly quoted table name - */ - public function quoteSimpleTableName($name) - { - return strpos($name, "`") !== false ? $name : "`" . $name . "`"; - } - - /** - * Quotes a column name for use in a query. - * A simple column name has no prefix. - * @param string $name column name - * @return string the properly quoted column name - */ - public function quoteSimpleColumnName($name) - { - return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; - } - - /** - * Creates a query builder for the MySQL database. - * @return QueryBuilder query builder instance - */ - public function createQueryBuilder() - { - return new QueryBuilder($this->connection); - } - - /** - * Loads the metadata for the specified table. - * @param string $name table name - * @return \yii\db\dao\TableSchema driver dependent table metadata. Null if the table does not exist. - */ - protected function loadTableSchema($name) - { - $table = new TableSchema; - $this->resolveTableNames($table, $name); - - if ($this->findColumns($table)) { - $this->findConstraints($table); - return $table; - } - } - - /** - * Resolves the table name and schema name (if any). - * @param \yii\db\dao\TableSchema $table the table metadata object - * @param string $name the table name - */ - protected function resolveTableNames($table, $name) - { - $parts = explode('.', str_replace('`', '', $name)); - if (isset($parts[1])) { - $table->schemaName = $parts[0]; - $table->name = $parts[1]; - $table->quotedName = $this->quoteSimpleTableName($table->schemaName) . '.' . $this->quoteSimpleTableName($table->name); - } else { - $table->name = $parts[0]; - $table->quotedName = $this->quoteSimpleTableName($table->name); - } - } - - /** - * Creates a table column. - * @param array $column column metadata - * @return ColumnSchema normalized column metadata - */ - protected function createColumn($column) - { - $c = new ColumnSchema; - - $c->name = $column['Field']; - $c->quotedName = $this->quoteSimpleColumnName($c->name); - $c->allowNull = $column['Null'] === 'YES'; - $c->isPrimaryKey = strpos($column['Key'], 'PRI') !== false; - $c->autoIncrement = stripos($column['Extra'], 'auto_increment') !== false; - - $c->dbType = $column['Type']; - $this->resolveColumnType($c); - $c->resolvePhpType(); - - $this->resolveColumnDefault($c, $column['Default']); - - return $c; - } - - /** - * Resolves the default value for the column. - * @param \yii\db\dao\ColumnSchema $column the column metadata object - * @param string $value the default value fetched from database - */ - protected function resolveColumnDefault($column, $value) - { - if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') { - $column->defaultValue = $column->typecast($value); - } - } - - /** - * Resolves the abstract data type for the column. - * @param \yii\db\dao\ColumnSchema $column the column metadata object - */ - public function resolveColumnType($column) - { - $column->type = self::TYPE_STRING; - $column->unsigned = strpos($column->dbType, 'unsigned') !== false; - - if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { - $type = $matches[1]; - if (isset($this->typeMap[$type])) { - $column->type = $this->typeMap[$type]; - } - - if (!empty($matches[2])) { - if ($type === 'enum') { - $values = explode(',', $matches[2]); - foreach ($values as $i => $value) { - $values[$i] = trim($value, "'"); - } - $column->enumValues = $values; - } else { - $values = explode(',', $matches[2]); - $column->size = $column->precision = (int)$values[0]; - if (isset($values[1])) { - $column->scale = (int)$values[1]; - } - if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { - $column->type = 'boolean'; - } elseif ($type === 'bit') { - if ($column->size > 32) { - $column->type = 'bigint'; - } elseif ($column->size === 32) { - $column->type = 'integer'; - } - } - } - } - } - } - - /** - * Collects the metadata of table columns. - * @param \yii\db\dao\TableSchema $table the table metadata - * @return boolean whether the table exists in the database - */ - protected function findColumns($table) - { - $sql = 'SHOW COLUMNS FROM ' . $table->quotedName; - try { - $columns = $this->connection->createCommand($sql)->queryAll(); - } catch (\Exception $e) { - return false; - } - foreach ($columns as $column) { - $column = $this->createColumn($column); - $table->columns[$column->name] = $column; - if ($column->isPrimaryKey) { - $table->primaryKey[] = $column->name; - if ($column->autoIncrement) { - $table->sequenceName = ''; - } - } - } - return true; - } - - /** - * Collects the foreign key column details for the given table. - * @param \yii\db\dao\TableSchema $table the table metadata - */ - protected function findConstraints($table) - { - $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $table->quotedName)->queryRow(); - if (isset($row['Create Table'])) { - $sql = $row['Create Table']; - } else { - $row = array_values($row); - $sql = $row[1]; - } - - $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; - if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); - $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); - $constraint = array(str_replace('`', '', $match[2])); - foreach ($fks as $k => $name) { - $constraint[$name] = $pks[$k]; - } - $table->foreignKeys[] = $constraint; - } - } - } - - /** - * Returns all table names in the database. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * If not empty, the returned table names will be prefixed with the schema name. - * @return array all table names in the database. - */ - protected function findTableNames($schema = '') - { - if ($schema === '') { - return $this->connection->createCommand('SHOW TABLES')->queryColumn(); - } - $sql = 'SHOW TABLES FROM ' . $this->quoteSimpleTableName($schema); - $names = $this->connection->createCommand($sql)->queryColumn(); - foreach ($names as $i => $name) { - $names[$i] = $schema . '.' . $name; - } - return $names; - } -} diff --git a/framework/db/dao/mysql/QueryBuilder.php b/framework/db/dao/mysql/QueryBuilder.php deleted file mode 100644 index 6e1a401..0000000 --- a/framework/db/dao/mysql/QueryBuilder.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @since 2.0 - */ -class QueryBuilder extends \yii\db\dao\QueryBuilder -{ - /** - * @var array mapping from abstract column types (keys) to physical column types (values). - */ - public $typeMap = array( - Driver::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', - Driver::TYPE_STRING => 'varchar(255)', - Driver::TYPE_TEXT => 'text', - Driver::TYPE_SMALLINT => 'smallint(6)', - Driver::TYPE_INTEGER => 'int(11)', - Driver::TYPE_BIGINT => 'bigint(20)', - Driver::TYPE_FLOAT => 'float', - Driver::TYPE_DECIMAL => 'decimal', - Driver::TYPE_DATETIME => 'datetime', - Driver::TYPE_TIMESTAMP => 'timestamp', - Driver::TYPE_TIME => 'time', - Driver::TYPE_DATE => 'date', - Driver::TYPE_BINARY => 'blob', - Driver::TYPE_BOOLEAN => 'tinyint(1)', - Driver::TYPE_MONEY => 'decimal(19,4)', - ); - - /** - * Builds a SQL statement for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $oldName the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return string the SQL statement for renaming a DB column. - */ - public function renameColumn($table, $oldName, $newName) - { - $quotedTable = $this->quoteTableName($table); - $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryRow(); - if ($row === false) { - throw new Exception("Unable to find '$oldName' in table '$table'."); - } - if (isset($row['Create Table'])) { - $sql = $row['Create Table']; - } else { - $row = array_values($row); - $sql = $row[1]; - } - if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) { - foreach ($matches[1] as $i => $c) { - if ($c === $oldName) { - return "ALTER TABLE $quotedTable CHANGE " - . $this->quoteColumnName($oldName, true) . ' ' - . $this->quoteColumnName($newName, true) . ' ' - . $matches[2][$i]; - } - } - } - // try to give back a SQL anyway - return "ALTER TABLE $quotedTable CHANGE " - . $this->quoteColumnName($oldName, true) . ' ' - . $this->quoteColumnName($newName, true); - } - - /** - * 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 string the SQL statement for dropping a foreign key constraint. - */ - public function dropForeignKey($name, $table) - { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' DROP FOREIGN KEY ' . $this->quoteColumnName($name); - } -} diff --git a/framework/db/dao/sqlite/Driver.php b/framework/db/dao/sqlite/Driver.php deleted file mode 100644 index bc127d4..0000000 --- a/framework/db/dao/sqlite/Driver.php +++ /dev/null @@ -1,203 +0,0 @@ - - * @since 2.0 - */ -class Driver extends \yii\db\dao\Driver -{ - /** - * @var array mapping from physical column types (keys) to abstract column types (values) - */ - public $typeMap = array( - 'tinyint' => self::TYPE_SMALLINT, - 'bit' => self::TYPE_SMALLINT, - 'smallint' => self::TYPE_SMALLINT, - 'mediumint' => self::TYPE_INTEGER, - 'int' => self::TYPE_INTEGER, - 'integer' => self::TYPE_INTEGER, - 'bigint' => self::TYPE_BIGINT, - 'float' => self::TYPE_FLOAT, - 'double' => self::TYPE_FLOAT, - 'real' => self::TYPE_FLOAT, - 'decimal' => self::TYPE_DECIMAL, - 'numeric' => self::TYPE_DECIMAL, - 'tinytext' => self::TYPE_TEXT, - 'mediumtext' => self::TYPE_TEXT, - 'longtext' => self::TYPE_TEXT, - 'text' => self::TYPE_TEXT, - 'varchar' => self::TYPE_STRING, - 'string' => self::TYPE_STRING, - 'char' => self::TYPE_STRING, - 'datetime' => self::TYPE_DATETIME, - 'year' => self::TYPE_DATE, - 'date' => self::TYPE_DATE, - 'time' => self::TYPE_TIME, - 'timestamp' => self::TYPE_TIMESTAMP, - 'enum' => self::TYPE_STRING, - ); - - /** - * Creates a query builder for the MySQL database. - * This method may be overridden by child classes to create a DBMS-specific query builder. - * @return QueryBuilder query builder instance - */ - public function createQueryBuilder() - { - return new QueryBuilder($this->connection); - } - - /** - * Returns all table names in the database. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * If not empty, the returned table names will be prefixed with the schema name. - * @return array all table names in the database. - */ - protected function findTableNames($schema = '') - { - $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; - return $this->connection->createCommand($sql)->queryColumn(); - } - - /** - * Loads the metadata for the specified table. - * @param string $name table name - * @return \yii\db\dao\TableSchema driver dependent table metadata. Null if the table does not exist. - */ - protected function loadTableSchema($name) - { - $table = new TableSchema; - $table->name = $name; - $table->quotedName = $this->quoteTableName($name); - - if ($this->findColumns($table)) { - $this->findConstraints($table); - return $table; - } - } - - /** - * Collects the table column metadata. - * @param \yii\db\dao\TableSchema $table the table metadata - * @return boolean whether the table exists in the database - */ - protected function findColumns($table) - { - $sql = "PRAGMA table_info({$table->quotedName})"; - $columns = $this->connection->createCommand($sql)->queryAll(); - if (empty($columns)) { - return false; - } - - foreach ($columns as $column) { - $column = $this->createColumn($column); - $table->columns[$column->name] = $column; - if ($column->isPrimaryKey) { - $table->primaryKey[] = $column->name; - } - } - if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) { - $table->sequenceName = ''; - $table->columns[$table->primaryKey[0]]->autoIncrement = true; - } - - return true; - } - - /** - * Collects the foreign key column details for the given table. - * @param \yii\db\dao\TableSchema $table the table metadata - */ - protected function findConstraints($table) - { - $sql = "PRAGMA foreign_key_list({$table->quotedName})"; - $keys = $this->connection->createCommand($sql)->queryAll(); - foreach ($keys as $key) { - $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); - } - } - - /** - * Creates a table column. - * @param array $column column metadata - * @return ColumnSchema normalized column metadata - */ - protected function createColumn($column) - { - $c = new ColumnSchema; - $c->name = $column['name']; - $c->quotedName = $this->quoteSimpleColumnName($c->name); - $c->allowNull = !$column['notnull']; - $c->isPrimaryKey = $column['pk'] != 0; - - $c->dbType = $column['type']; - $this->resolveColumnType($c); - $c->resolvePhpType(); - - $this->resolveColumnDefault($c, $column['dflt_value']); - - return $c; - } - - /** - * Resolves the abstract data type for the column. - * @param \yii\db\dao\ColumnSchema $column the column metadata object - */ - public function resolveColumnType($column) - { - $column->type = self::TYPE_STRING; - $column->unsigned = strpos($column->dbType, 'unsigned') !== false; - - if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { - $type = $matches[1]; - if (isset($this->typeMap[$type])) { - $column->type = $this->typeMap[$type]; - } - - if (!empty($matches[2])) { - $values = explode(',', $matches[2]); - $column->size = $column->precision = (int)$values[0]; - if (isset($values[1])) { - $column->scale = (int)$values[1]; - } - if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { - $column->type = 'boolean'; - } elseif ($type === 'bit') { - if ($column->size > 32) { - $column->type = 'bigint'; - } elseif ($column->size === 32) { - $column->type = 'integer'; - } - } - } - } - } - - /** - * Resolves the default value for the column. - * @param \yii\db\dao\ColumnSchema $column the column metadata object - * @param string $value the default value fetched from database - */ - protected function resolveColumnDefault($column, $value) - { - if ($column->type === 'string') { - $column->defaultValue = trim($value, "'\""); - } else { - $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null); - } - } -} diff --git a/framework/db/dao/sqlite/QueryBuilder.php b/framework/db/dao/sqlite/QueryBuilder.php deleted file mode 100644 index 12d0710..0000000 --- a/framework/db/dao/sqlite/QueryBuilder.php +++ /dev/null @@ -1,149 +0,0 @@ - - * @since 2.0 - */ -class QueryBuilder extends \yii\db\dao\QueryBuilder -{ - /** - * @var array mapping from abstract column types (keys) to physical column types (values). - */ - public $typeMap = array(Driver::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Driver::TYPE_STRING => 'varchar(255)', Driver::TYPE_TEXT => 'text', Driver::TYPE_SMALLINT => 'smallint', Driver::TYPE_INTEGER => 'integer', Driver::TYPE_BIGINT => 'bigint', Driver::TYPE_FLOAT => 'float', Driver::TYPE_DECIMAL => 'decimal', Driver::TYPE_DATETIME => 'datetime', Driver::TYPE_TIMESTAMP => 'timestamp', Driver::TYPE_TIME => 'time', Driver::TYPE_DATE => 'date', Driver::TYPE_BINARY => 'blob', Driver::TYPE_BOOLEAN => 'tinyint(1)', Driver::TYPE_MONEY => 'decimal(19,4)',); - - /** - * Resets the sequence value of a table's primary key. - * The sequence will be reset such that the primary key of the next new row inserted - * will have the specified value or 1. - * @param string $table the table schema whose primary key sequence will be reset - * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. - */ - public function resetSequence($table, $value = null) - { - if ($table->sequenceName !== null) { - if ($value === null) { - $value = $this->connection->createCommand("SELECT MAX(`{$table->primaryKey[0]}`) FROM {$table->quotedName}")->queryScalar(); - } else { - $value = (int)$value - 1; - } - try { - // it's possible sqlite_sequence does not exist - $this->connection->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute(); - } catch (Exception $e) { - } - } - } - - /** - * Enables or disables integrity check. - * @param boolean $check whether to turn on or off the integrity check. - * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - */ - public function checkIntegrity($check = true, $schema = '') - { - // SQLite doesn't enforce integrity - } - - /** - * Builds 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 string the SQL statement for truncating a DB table. - */ - public function truncateTable($table) - { - return "DELETE FROM " . $this->quoteTableName($table); - } - - /** - * Builds 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 string the SQL statement for dropping an index. - */ - public function dropIndex($name, $table) - { - return 'DROP INDEX ' . $this->quoteTableName($name); - } - - /** - * Builds 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 string the SQL statement for dropping a DB column. - */ - public function dropColumn($table, $column) - { - throw new Exception(__METHOD__ . ' is not supported by SQLite.'); - } - - /** - * Builds a SQL statement for renaming a column. - * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. - * @param string $oldName the old name of the column. The name will be properly quoted by the method. - * @param string $newName the new name of the column. The name will be properly quoted by the method. - * @return string the SQL statement for renaming a DB column. - */ - public function renameColumn($table, $oldName, $newName) - { - throw new Exception(__METHOD__ . ' is not supported by SQLite.'); - } - - /** - * 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|array $columns the name of the column to that the constraint will be added on. - * If there are multiple columns, separate them with commas or use an array to represent them. - * @param string $refTable the table that the foreign key references to. - * @param string|array $refColumns the name of the column that the foreign key references to. - * If there are multiple columns, separate them with commas or use an array to represent them. - * @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 string the SQL statement for adding a foreign key constraint to an existing table. - */ - public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) - { - throw new Exception(__METHOD__ . ' is not supported by SQLite.'); - } - - /** - * 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 string the SQL statement for dropping a foreign key constraint. - */ - public function dropForeignKey($name, $table) - { - throw new Exception(__METHOD__ . ' is not supported by SQLite.'); - } - - /** - * Builds 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 [[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 string the SQL statement for changing the definition of a column. - */ - public function alterColumn($table, $column, $type) - { - throw new Exception(__METHOD__ . ' is not supported by SQLite.'); - } -} diff --git a/framework/db/mysql/Driver.php b/framework/db/mysql/Driver.php new file mode 100644 index 0000000..654db54 --- /dev/null +++ b/framework/db/mysql/Driver.php @@ -0,0 +1,269 @@ + + * @since 2.0 + */ +class Driver extends \yii\db\Driver +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = array( + 'tinyint' => self::TYPE_SMALLINT, + 'bit' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'mediumint' => self::TYPE_INTEGER, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'float' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'decimal' => self::TYPE_DECIMAL, + 'numeric' => self::TYPE_DECIMAL, + 'tinytext' => self::TYPE_TEXT, + 'mediumtext' => self::TYPE_TEXT, + 'longtext' => self::TYPE_TEXT, + 'text' => self::TYPE_TEXT, + 'varchar' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + 'char' => self::TYPE_STRING, + 'datetime' => self::TYPE_DATETIME, + 'year' => self::TYPE_DATE, + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'enum' => self::TYPE_STRING, + ); + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + } + + /** + * Creates a query builder for the MySQL database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->connection); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return \yii\db\TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $table = new TableSchema; + $this->resolveTableNames($table, $name); + + if ($this->findColumns($table)) { + $this->findConstraints($table); + return $table; + } + } + + /** + * Resolves the table name and schema name (if any). + * @param \yii\db\TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) + { + $parts = explode('.', str_replace('`', '', $name)); + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + $table->quotedName = $this->quoteSimpleTableName($table->schemaName) . '.' . $this->quoteSimpleTableName($table->name); + } else { + $table->name = $parts[0]; + $table->quotedName = $this->quoteSimpleTableName($table->name); + } + } + + /** + * Creates a table column. + * @param array $column column metadata + * @return ColumnSchema normalized column metadata + */ + protected function createColumn($column) + { + $c = new ColumnSchema; + + $c->name = $column['Field']; + $c->quotedName = $this->quoteSimpleColumnName($c->name); + $c->allowNull = $column['Null'] === 'YES'; + $c->isPrimaryKey = strpos($column['Key'], 'PRI') !== false; + $c->autoIncrement = stripos($column['Extra'], 'auto_increment') !== false; + + $c->dbType = $column['Type']; + $this->resolveColumnType($c); + $c->resolvePhpType(); + + $this->resolveColumnDefault($c, $column['Default']); + + return $c; + } + + /** + * Resolves the default value for the column. + * @param \yii\db\ColumnSchema $column the column metadata object + * @param string $value the default value fetched from database + */ + protected function resolveColumnDefault($column, $value) + { + if ($column->type !== 'timestamp' || $value !== 'CURRENT_TIMESTAMP') { + $column->defaultValue = $column->typecast($value); + } + } + + /** + * Resolves the abstract data type for the column. + * @param \yii\db\ColumnSchema $column the column metadata object + */ + public function resolveColumnType($column) + { + $column->type = self::TYPE_STRING; + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + + if (!empty($matches[2])) { + if ($type === 'enum') { + $values = explode(',', $matches[2]); + foreach ($values as $i => $value) { + $values[$i] = trim($value, "'"); + } + $column->enumValues = $values; + } else { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int)$values[0]; + if (isset($values[1])) { + $column->scale = (int)$values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + } + } + + /** + * Collects the metadata of table columns. + * @param \yii\db\TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) + { + $sql = 'SHOW COLUMNS FROM ' . $table->quotedName; + try { + $columns = $this->connection->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + foreach ($columns as $column) { + $column = $this->createColumn($column); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; + } + } + } + return true; + } + + /** + * Collects the foreign key column details for the given table. + * @param \yii\db\TableSchema $table the table metadata + */ + protected function findConstraints($table) + { + $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $table->quotedName)->queryRow(); + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + + $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; + if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); + $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); + $constraint = array(str_replace('`', '', $match[2])); + foreach ($fks as $k => $name) { + $constraint[$name] = $pks[$k]; + } + $table->foreignKeys[] = $constraint; + } + } + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @return array all table names in the database. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + return $this->connection->createCommand('SHOW TABLES')->queryColumn(); + } + $sql = 'SHOW TABLES FROM ' . $this->quoteSimpleTableName($schema); + $names = $this->connection->createCommand($sql)->queryColumn(); + foreach ($names as $i => $name) { + $names[$i] = $schema . '.' . $name; + } + return $names; + } +} diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php new file mode 100644 index 0000000..41cbc2c --- /dev/null +++ b/framework/db/mysql/QueryBuilder.php @@ -0,0 +1,90 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array( + Driver::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Driver::TYPE_STRING => 'varchar(255)', + Driver::TYPE_TEXT => 'text', + Driver::TYPE_SMALLINT => 'smallint(6)', + Driver::TYPE_INTEGER => 'int(11)', + Driver::TYPE_BIGINT => 'bigint(20)', + Driver::TYPE_FLOAT => 'float', + Driver::TYPE_DECIMAL => 'decimal', + Driver::TYPE_DATETIME => 'datetime', + Driver::TYPE_TIMESTAMP => 'timestamp', + Driver::TYPE_TIME => 'time', + Driver::TYPE_DATE => 'date', + Driver::TYPE_BINARY => 'blob', + Driver::TYPE_BOOLEAN => 'tinyint(1)', + Driver::TYPE_MONEY => 'decimal(19,4)', + ); + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + */ + public function renameColumn($table, $oldName, $newName) + { + $quotedTable = $this->quoteTableName($table); + $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryRow(); + if ($row === false) { + throw new Exception("Unable to find '$oldName' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $oldName) { + return "ALTER TABLE $quotedTable CHANGE " + . $this->quoteColumnName($oldName, true) . ' ' + . $this->quoteColumnName($newName, true) . ' ' + . $matches[2][$i]; + } + } + } + // try to give back a SQL anyway + return "ALTER TABLE $quotedTable CHANGE " + . $this->quoteColumnName($oldName, true) . ' ' + . $this->quoteColumnName($newName, true); + } + + /** + * 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 string the SQL statement for dropping a foreign key constraint. + */ + public function dropForeignKey($name, $table) + { + return 'ALTER TABLE ' . $this->quoteTableName($table) + . ' DROP FOREIGN KEY ' . $this->quoteColumnName($name); + } +} diff --git a/framework/db/sqlite/Driver.php b/framework/db/sqlite/Driver.php new file mode 100644 index 0000000..d88e5c9 --- /dev/null +++ b/framework/db/sqlite/Driver.php @@ -0,0 +1,203 @@ + + * @since 2.0 + */ +class Driver extends \yii\db\Driver +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = array( + 'tinyint' => self::TYPE_SMALLINT, + 'bit' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'mediumint' => self::TYPE_INTEGER, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'float' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'decimal' => self::TYPE_DECIMAL, + 'numeric' => self::TYPE_DECIMAL, + 'tinytext' => self::TYPE_TEXT, + 'mediumtext' => self::TYPE_TEXT, + 'longtext' => self::TYPE_TEXT, + 'text' => self::TYPE_TEXT, + 'varchar' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + 'char' => self::TYPE_STRING, + 'datetime' => self::TYPE_DATETIME, + 'year' => self::TYPE_DATE, + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'enum' => self::TYPE_STRING, + ); + + /** + * Creates a query builder for the MySQL database. + * This method may be overridden by child classes to create a DBMS-specific query builder. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->connection); + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @return array all table names in the database. + */ + protected function findTableNames($schema = '') + { + $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; + return $this->connection->createCommand($sql)->queryColumn(); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return \yii\db\TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $table = new TableSchema; + $table->name = $name; + $table->quotedName = $this->quoteTableName($name); + + if ($this->findColumns($table)) { + $this->findConstraints($table); + return $table; + } + } + + /** + * Collects the table column metadata. + * @param \yii\db\TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) + { + $sql = "PRAGMA table_info({$table->quotedName})"; + $columns = $this->connection->createCommand($sql)->queryAll(); + if (empty($columns)) { + return false; + } + + foreach ($columns as $column) { + $column = $this->createColumn($column); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + } + } + if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) { + $table->sequenceName = ''; + $table->columns[$table->primaryKey[0]]->autoIncrement = true; + } + + return true; + } + + /** + * Collects the foreign key column details for the given table. + * @param \yii\db\TableSchema $table the table metadata + */ + protected function findConstraints($table) + { + $sql = "PRAGMA foreign_key_list({$table->quotedName})"; + $keys = $this->connection->createCommand($sql)->queryAll(); + foreach ($keys as $key) { + $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); + } + } + + /** + * Creates a table column. + * @param array $column column metadata + * @return ColumnSchema normalized column metadata + */ + protected function createColumn($column) + { + $c = new ColumnSchema; + $c->name = $column['name']; + $c->quotedName = $this->quoteSimpleColumnName($c->name); + $c->allowNull = !$column['notnull']; + $c->isPrimaryKey = $column['pk'] != 0; + + $c->dbType = $column['type']; + $this->resolveColumnType($c); + $c->resolvePhpType(); + + $this->resolveColumnDefault($c, $column['dflt_value']); + + return $c; + } + + /** + * Resolves the abstract data type for the column. + * @param \yii\db\ColumnSchema $column the column metadata object + */ + public function resolveColumnType($column) + { + $column->type = self::TYPE_STRING; + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + + if (!empty($matches[2])) { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int)$values[0]; + if (isset($values[1])) { + $column->scale = (int)$values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + } + + /** + * Resolves the default value for the column. + * @param \yii\db\ColumnSchema $column the column metadata object + * @param string $value the default value fetched from database + */ + protected function resolveColumnDefault($column, $value) + { + if ($column->type === 'string') { + $column->defaultValue = trim($value, "'\""); + } else { + $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null); + } + } +} diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php new file mode 100644 index 0000000..e226390 --- /dev/null +++ b/framework/db/sqlite/QueryBuilder.php @@ -0,0 +1,149 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array(Driver::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Driver::TYPE_STRING => 'varchar(255)', Driver::TYPE_TEXT => 'text', Driver::TYPE_SMALLINT => 'smallint', Driver::TYPE_INTEGER => 'integer', Driver::TYPE_BIGINT => 'bigint', Driver::TYPE_FLOAT => 'float', Driver::TYPE_DECIMAL => 'decimal', Driver::TYPE_DATETIME => 'datetime', Driver::TYPE_TIMESTAMP => 'timestamp', Driver::TYPE_TIME => 'time', Driver::TYPE_DATE => 'date', Driver::TYPE_BINARY => 'blob', Driver::TYPE_BOOLEAN => 'tinyint(1)', Driver::TYPE_MONEY => 'decimal(19,4)',); + + /** + * Resets the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $table the table schema whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + */ + public function resetSequence($table, $value = null) + { + if ($table->sequenceName !== null) { + if ($value === null) { + $value = $this->connection->createCommand("SELECT MAX(`{$table->primaryKey[0]}`) FROM {$table->quotedName}")->queryScalar(); + } else { + $value = (int)$value - 1; + } + try { + // it's possible sqlite_sequence does not exist + $this->connection->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute(); + } catch (Exception $e) { + } + } + } + + /** + * Enables or disables integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + */ + public function checkIntegrity($check = true, $schema = '') + { + // SQLite doesn't enforce integrity + } + + /** + * Builds 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 string the SQL statement for truncating a DB table. + */ + public function truncateTable($table) + { + return "DELETE FROM " . $this->quoteTableName($table); + } + + /** + * Builds 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 string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->quoteTableName($name); + } + + /** + * Builds 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 string the SQL statement for dropping a DB column. + */ + public function dropColumn($table, $column) + { + throw new Exception(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + */ + public function renameColumn($table, $oldName, $newName) + { + throw new Exception(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * 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|array $columns the name of the column to that the constraint will be added on. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $refTable the table that the foreign key references to. + * @param string|array $refColumns the name of the column that the foreign key references to. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @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 string the SQL statement for adding a foreign key constraint to an existing table. + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + throw new Exception(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * 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 string the SQL statement for dropping a foreign key constraint. + */ + public function dropForeignKey($name, $table) + { + throw new Exception(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds 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 [[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 string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + throw new Exception(__METHOD__ . ' is not supported by SQLite.'); + } +} diff --git a/framework/logging/DbTarget.php b/framework/logging/DbTarget.php index 56a27bc..10d463e 100644 --- a/framework/logging/DbTarget.php +++ b/framework/logging/DbTarget.php @@ -23,7 +23,7 @@ namespace yii\logging; class DbTarget extends Target { /** - * @var string the ID of [[\yii\db\dao\Connection]] application component. + * @var string the ID of [[\yii\db\Connection]] application component. * Defaults to 'db'. Please make sure that your database contains a table * whose name is as specified in [[tableName]] and has the required table structure. * @see tableName @@ -31,7 +31,7 @@ class DbTarget extends Target public $connectionID = 'db'; /** * @var string the name of the DB table that stores log messages. Defaults to '{{log}}'. - * If you are using table prefix 'tbl_' (configured via [[\yii\db\dao\Connection::tablePrefix]]), + * If you are using table prefix 'tbl_' (configured via [[\yii\db\Connection::tablePrefix]]), * it means the DB table would be named as 'tbl_log'. * * The DB table must have the following structure: @@ -62,14 +62,14 @@ class DbTarget extends Target /** * Returns the DB connection used for saving log messages. - * @return \yii\db\dao\Connection the DB connection instance + * @return \yii\db\Connection the DB connection instance * @throws \yii\base\Exception if [[connectionID]] does not refer to a valid application component ID. */ public function getDbConnection() { if ($this->_db === null) { $this->_db = \Yii::$application->getComponent($this->connectionID); - if (!$this->_db instanceof \yii\db\dao\Connection) { + if (!$this->_db instanceof \yii\db\Connection) { throw new \yii\base\Exception('DbTarget.connectionID must refer to a valid application component ID'); } } diff --git a/framework/logging/Logger.php b/framework/logging/Logger.php index 2f0cf89..19f0f06 100644 --- a/framework/logging/Logger.php +++ b/framework/logging/Logger.php @@ -190,7 +190,7 @@ class Logger extends \yii\base\Component * @param array $categories list of categories that you are interested in. * You can use an asterisk at the end of a category to do a prefix match. * For example, 'yii\db\*' will match categories starting with 'yii\db\', - * such as 'yii\db\dao\Connection'. + * such as 'yii\db\Connection'. * @param array $excludeCategories list of categories that you are interested in. * @return array the profiling results. Each array element has the following structure: * `array($token, $category, $time)`. diff --git a/framework/logging/Target.php b/framework/logging/Target.php index 1a1979a..88cb5cb 100644 --- a/framework/logging/Target.php +++ b/framework/logging/Target.php @@ -37,7 +37,7 @@ abstract class Target extends \yii\base\Component * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. * You can use an asterisk at the end of a category so that the category may be used to * match those categories sharing the same common prefix. For example, 'yii\db\*' will match - * categories starting with 'yii\db\', such as 'yii\db\dao\Connection'. + * categories starting with 'yii\db\', such as 'yii\db\Connection'. */ public $categories = array(); /** @@ -45,7 +45,7 @@ abstract class Target extends \yii\base\Component * If this property is not empty, then any category listed here will be excluded from [[categories]]. * You can use an asterisk at the end of a category so that the category can be used to * match those categories sharing the same common prefix. For example, 'yii\db\*' will match - * categories starting with 'yii\db\', such as 'yii\db\dao\Connection'. + * categories starting with 'yii\db\', such as 'yii\db\Connection'. * @see categories */ public $except = array(); diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php index 9ef86ff..7f62ee9 100644 --- a/framework/validators/ExistValidator.php +++ b/framework/validators/ExistValidator.php @@ -21,22 +21,22 @@ namespace yii\validators; class ExistValidator extends Validator { /** - * @var string the yii\db\ar\ActiveRecord class name or alias of the class + * @var string the yii\db\ActiveRecord class name or alias of the class * that should be used to look for the attribute value being validated. - * Defaults to null, meaning using the yii\db\ar\ActiveRecord class of + * Defaults to null, meaning using the yii\db\ActiveRecord class of * the attribute being validated. * @see attributeName */ public $className; /** - * @var string the yii\db\ar\ActiveRecord class attribute name that should be + * @var string the yii\db\ActiveRecord class attribute name that should be * used to look for the attribute value being validated. Defaults to null, * meaning using the name of the attribute being validated. * @see className */ public $attributeName; /** - * @var \yii\db\dao\BaseQuery additional query criteria. This will be combined + * @var \yii\db\BaseQuery additional query criteria. This will be combined * with the condition that checks if the attribute value exists in the * corresponding table column. */ @@ -51,7 +51,7 @@ class ExistValidator extends Validator * Validates the attribute of the object. * If there is any error, the error message is added to the object. * - * @param \yii\db\ar\ActiveRecord $object the object being validated + * @param \yii\db\ActiveRecord $object the object being validated * @param string $attribute the attribute being validated * * @throws \yii\base\Exception if table doesn't have column specified @@ -72,7 +72,7 @@ class ExistValidator extends Validator $finder = $object->find()->where(array($column->name => $value)); - if ($this->query instanceof \yii\db\dao\BaseQuery) { + if ($this->query instanceof \yii\db\BaseQuery) { $finder->mergeWith($this->query); } diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 0366792..df6147f 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -28,7 +28,7 @@ class UniqueValidator extends Validator */ public $allowEmpty = true; /** - * @var string the yii\db\ar\ActiveRecord class name or alias of the class + * @var string the yii\db\ActiveRecord class name or alias of the class * that should be used to look for the attribute value being validated. * Defaults to null, meaning using the class of the object currently * being validated. @@ -43,7 +43,7 @@ class UniqueValidator extends Validator */ public $attributeName; /** - * @var \yii\db\ar\ActiveQuery additional query criteria. This will be + * @var \yii\db\ActiveQuery additional query criteria. This will be * combined with the condition that checks if the attribute value exists * in the corresponding table column. */ @@ -87,7 +87,7 @@ class UniqueValidator extends Validator $finder->where($this->caseSensitive ? "{$column->quotedName}=:value" : "LOWER({$column->quotedName})=LOWER(:value)"); $finder->params(array(':value' => $value)); - if ($this->query instanceof \yii\db\dao\BaseQuery) { + if ($this->query instanceof \yii\db\BaseQuery) { $finder->mergeWith($this->query); } diff --git a/tests/unit/MysqlTestCase.php b/tests/unit/MysqlTestCase.php index c933bcc..5fe7dbd 100644 --- a/tests/unit/MysqlTestCase.php +++ b/tests/unit/MysqlTestCase.php @@ -13,12 +13,12 @@ class MysqlTestCase extends TestCase /** * @param bool $reset whether to clean up the test database - * @return \yii\db\dao\Connection + * @return \yii\db\Connection */ function getConnection($reset = true) { $params = $this->getParam('mysql'); - $db = new \yii\db\dao\Connection; + $db = new \yii\db\Connection; $db->dsn = $params['dsn']; $db->username = $params['username']; $db->password = $params['password']; diff --git a/tests/unit/data/ar/ActiveRecord.php b/tests/unit/data/ar/ActiveRecord.php index aafc99f..71ad83f 100644 --- a/tests/unit/data/ar/ActiveRecord.php +++ b/tests/unit/data/ar/ActiveRecord.php @@ -9,7 +9,7 @@ namespace yiiunit\data\ar; -use yii\db\dao\Connection; +use yii\db\Connection; /** * ActiveRecord is ... @@ -17,7 +17,7 @@ use yii\db\dao\Connection; * @author Qiang Xue * @since 2.0 */ -class ActiveRecord extends \yii\db\ar\ActiveRecord +class ActiveRecord extends \yii\db\ActiveRecord { public static $db; diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index 05b8de1..5c42ecf 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -1,7 +1,7 @@