diff --git a/framework/base/BadCallException.php b/framework/base/BadCallException.php new file mode 100644 index 0000000..35c0b4a --- /dev/null +++ b/framework/base/BadCallException.php @@ -0,0 +1,21 @@ + + * @since 2.0 + */ +class BadCallException extends \Exception +{ +} + diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 860a388..830d145 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -45,7 +45,7 @@ use yii\util\StringHelper; * @author Qiang Xue * @since 2.0 */ -abstract class ActiveRecord extends Model +class ActiveRecord extends Model { /** * @var array attribute values indexed by attribute names diff --git a/framework/db/Command.php b/framework/db/Command.php index 6861024..811e993 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -21,7 +21,7 @@ namespace yii\db; * For example, * * ~~~ - * $users = \Yii::$application->db->createCommand('SELECT * FROM tbl_user')->queryAll(); + * $users = $connection->createCommand('SELECT * FROM tbl_user')->queryAll(); * ~~~ * * Command supports SQL statement preparation and parameter binding. @@ -30,6 +30,18 @@ namespace yii\db; * When binding a parameter, the SQL statement is automatically prepared. * You may also call [[prepare()]] explicitly to prepare a SQL statement. * + * Command also supports building SQL statements by providing methods such as [[insert()]], + * [[update()]], etc. For example, + * + * ~~~ + * $connection->createCommand()->insert('tbl_user', array( + * 'name' => 'Sam', + * 'age' => 30, + * ))->execute(); + * ~~~ + * + * To build SELECT SQL statements, please use [[QueryBuilder]] instead. + * * @property string $sql the SQL statement to be executed * * @author Qiang Xue @@ -42,7 +54,7 @@ class Command extends \yii\base\Component */ public $connection; /** - * @var \PDOStatement the PDOStatement object that this command contains + * @var \PDOStatement the PDOStatement object that this command is associated with */ public $pdoStatement; /** @@ -78,23 +90,33 @@ class Command extends \yii\base\Component { if ($sql !== $this->_sql) { if ($this->connection->enableAutoQuoting && $sql != '') { - $sql = preg_replace_callback('/(\\{\\{(.*?)\\}\\}|\\[\\[(.*?)\\]\\])/', function($matches) { - if (isset($matches[3])) { - return $this->connection->quoteColumnName($matches[3]); - } else { - $name = str_replace('%', $this->connection->tablePrefix, $matches[2]); - return $this->connection->quoteTableName($name); - } - }, $sql); + $sql = $this->expandSql($sql); } + $this->cancel(); $this->_sql = $sql; $this->_params = array(); - $this->cancel(); } return $this; } /** + * Expands a SQL statement by quoting table and column names and replacing table prefixes. + * @param string $sql the SQL to be expanded + * @return string the expanded SQL + */ + protected function expandSql($sql) + { + return preg_replace_callback('/(\\{\\{(.*?)\\}\\}|\\[\\[(.*?)\\]\\])/', function($matches) { + if (isset($matches[3])) { + return $this->connection->quoteColumnName($matches[3]); + } else { + $name = str_replace('%', $this->connection->tablePrefix, $matches[2]); + return $this->connection->quoteTableName($name); + } + }, $sql); + } + + /** * Prepares the SQL statement to be executed. * For complex SQL statement that is to be executed multiple times, * this may improve performance. @@ -142,7 +164,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindParam($name, $value, $this->getPdoType(gettype($value))); + $this->pdoStatement->bindParam($name, $value, $this->getPdoType($value)); } elseif ($length === null) { $this->pdoStatement->bindParam($name, $value, $dataType); } elseif ($driverOptions === null) { @@ -169,7 +191,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->getPdoType(gettype($value))); + $this->pdoStatement->bindValue($name, $value, $this->getPdoType($value)); } else { $this->pdoStatement->bindValue($name, $value, $dataType); } @@ -197,7 +219,7 @@ class Command extends \yii\base\Component $type = $value[1]; $value = $value[0]; } else { - $type = $this->getPdoType(gettype($value)); + $type = $this->getPdoType($value); } $this->pdoStatement->bindValue($name, $value, $type); $this->_params[$name] = $value; @@ -207,12 +229,12 @@ class Command extends \yii\base\Component } /** - * 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 + * Determines the PDO type for the give PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type * @see http://www.php.net/manual/en/pdo.constants.php */ - private function getPdoType($type) + private function getPdoType($data) { static $typeMap = array( 'boolean' => \PDO::PARAM_BOOL, @@ -220,6 +242,7 @@ class Command extends \yii\base\Component 'string' => \PDO::PARAM_STR, 'NULL' => \PDO::PARAM_NULL, ); + $type = gettype($data); return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; } @@ -267,7 +290,9 @@ class Command extends \yii\base\Component \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); } @@ -364,6 +389,7 @@ class Command extends \yii\base\Component * @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 + * @throws Exception if the query causes any problem */ private function queryInternal($method, $params, $fetchMode = null) { @@ -439,9 +465,9 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $db->createCommand()->insert('tbl_user', array( - * 'name' => 'Sam', - * 'age' => 30, + * $connection->createCommand()->insert('tbl_user', array( + * 'name' => 'Sam', + * 'age' => 30, * ))->execute(); * ~~~ * @@ -465,8 +491,8 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $db->createCommand()->update('tbl_user', array( - * 'status' => 1, + * $connection->createCommand()->update('tbl_user', array( + * 'status' => 1, * ), 'age > 30')->execute(); * ~~~ * @@ -492,7 +518,7 @@ class Command extends \yii\base\Component * For example, * * ~~~ - * $db->createCommand()->delete('tbl_user', 'status = 0')->execute(); + * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute(); * ~~~ * * The method will properly escape the table and column names. diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 2b9530e..5fd624f 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -92,8 +92,7 @@ use yii\base\BadConfigException; * @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. + * @property array $querySummary The statistical results of SQL queries. * * @event Event afterOpen this event is triggered after a DB connection is established * @@ -118,7 +117,7 @@ class Connection extends \yii\base\ApplicationComponent */ public $password = ''; /** - * @var array PDO attributes (name=>value) that should be set when calling [[open]] + * @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. @@ -275,16 +274,6 @@ class Connection extends \yii\base\ApplicationComponent } /** - * 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 */ @@ -533,36 +522,12 @@ class Connection extends \yii\base\ApplicationComponent if (($pos = strpos($this->dsn, ':')) !== false) { return strtolower(substr($this->dsn, 0, $pos)); } else { - return strtolower($this->getAttribute(\PDO::ATTR_DRIVER_NAME)); + return strtolower($this->pdo->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. + * Returns the statistical results of SQL queries. * 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. @@ -570,7 +535,7 @@ class Connection extends \yii\base\ApplicationComponent * and the second element the total time spent in SQL execution. * @see \yii\logging\Logger::getProfiling() */ - public function getExecutionSummary() + public function getQuerySummary() { $logger = \Yii::getLogger(); $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php index e61ee45..a5be116 100644 --- a/framework/db/DataReader.php +++ b/framework/db/DataReader.php @@ -9,23 +9,33 @@ namespace yii\db; -use yii\db\Exception; +use yii\base\BadCallException; /** * DataReader represents a forward-only stream of rows from a query result set. * - * To read the current row of data, call [[read]]. The method [[readAll]] - * returns all the rows in a single array. - * - * One can also retrieve the rows of data in DataReader by using `foreach`: + * To read the current row of data, call [[read()]]. The method [[readAll()]] + * returns all the rows in a single array. Rows of data can also be read by + * iterating through the reader. For example, * * ~~~ + * $reader = $command->query('SELECT * FROM tbl_post'); + * + * while ($row = $reader->read()) { + * $rows[] = $row; + * } + * + * // equivalent to: * foreach($reader as $row) { - * // $row represents a row of data + * $rows[] = $row; * } + * + * // equivalent to: + * $rows = $reader->readAll(); * ~~~ * - * Since DataReader is a forward-only stream, you can only traverse it once. + * Note that since DataReader is a forward-only stream, you can only traverse it once. + * Doing it the second time will throw an exception. * * It is possible to use a specific mode of data fetching by setting * [[fetchMode]]. See the [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) @@ -202,7 +212,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable /** * Resets the iterator to the initial state. * This method is required by the interface Iterator. - * @throws Exception if this method is invoked twice + * @throws BadCallException if this method is invoked twice */ public function rewind() { @@ -210,7 +220,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable $this->_row = $this->_statement->fetch(); $this->_index = 0; } else { - throw new Exception('DataReader cannot rewind. It is a forward-only reader.'); + throw new BadCallException('DataReader cannot rewind. It is a forward-only reader.'); } }