From 64c5da53b0356b2e7c726cd9c7651e3d148871b7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 1 Sep 2011 22:04:46 -0400 Subject: [PATCH] w --- framework/db/dao/Command.php | 824 ++++++++------------------------------ framework/db/dao/Connection.php | 5 + framework/db/dao/DataReader.php | 2 + framework/db/dao/Query.php | 167 +------- framework/db/dao/QueryBuilder.php | 155 +++++-- 5 files changed, 306 insertions(+), 847 deletions(-) diff --git a/framework/db/dao/Command.php b/framework/db/dao/Command.php index ecb10fb..3fa762f 100644 --- a/framework/db/dao/Command.php +++ b/framework/db/dao/Command.php @@ -50,12 +50,19 @@ class Command extends \yii\base\Component */ public $params = array(); - private $_connection; - private $_text; - private $_statement; + public $connection; + public $query; + public $pdoStatement; + + private $_sql; private $_paramLog = array(); - private $_query; - private $_fetchMode = array(PDO::FETCH_ASSOC); + /** + * Set the default fetch mode for this statement + * @param mixed $mode fetch mode + * @return Command + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + */ + public $fetchMode = \PDO::FETCH_ASSOC; /** * Constructor. @@ -80,38 +87,19 @@ class Command extends \yii\base\Component */ public function __construct($connection, $query = null) { - $this->_connection = $connection; - if (is_array($query)) - { - foreach ($query as $name => $value) - $this->$name = $value; + $this->connection = $connection; + if (is_object($query)) { + $this->query = $query; + } + else { + $this->query = new Query; + if (is_array($this->query)) { + $this->query->fromArray($this->query); + } + else { + $this->_sql = $query; + } } - else - $this->setText($query); - } - - /** - * Set the statement to null when serializing. - * @return array - */ - public function __sleep() - { - $this->_statement = null; - return array_keys(get_object_vars($this)); - } - - /** - * Set the default fetch mode for this statement - * @param mixed $mode fetch mode - * @return Command - * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php - * @since 1.1.7 - */ - public function setFetchMode($mode) - { - $params = func_get_args(); - $this->_fetchMode = $params; - return $this; } /** @@ -124,9 +112,9 @@ class Command extends \yii\base\Component */ public function reset() { - $this->_text = null; - $this->_query = null; - $this->_statement = null; + $this->_sql = null; + $this->query = new Query; + $this->pdoStatement = null; $this->_paramLog = array(); $this->params = array(); return $this; @@ -135,11 +123,12 @@ class Command extends \yii\base\Component /** * @return string the SQL statement to be executed */ - public function getText() + public function getSql() { - if ($this->_text == '' && !empty($this->_query)) - $this->setText($this->buildQuery($this->_query)); - return $this->_text; + if ($this->_sql == '' && is_object($this->query)) { + $this->_sql = $this->query->getSql($this->connection); + } + return $this->_sql; } /** @@ -148,34 +137,19 @@ class Command extends \yii\base\Component * @param string $value the SQL statement to be executed * @return Command this command instance */ - public function setText($value) + public function setSql($value) { - if ($this->_connection->tablePrefix !== null && $value != '') - $this->_text = preg_replace('/{{(.*?)}}/', $this->_connection->tablePrefix . '\1', $value); - else - $this->_text = $value; + if ($this->connection->tablePrefix !== null && strpos($value, '{') !== false) { + $this->_sql = preg_replace('/{{(.*?)}}/', $this->connection->tablePrefix . '\1', $value); + } + else { + $this->_sql = $value; + } $this->cancel(); return $this; } /** - * @return CDbConnection the connection associated with this command - */ - public function getConnection() - { - return $this->_connection; - } - - /** - * @return PDOStatement the underlying PDOStatement for this command - * It could be null if the statement is not prepared yet. - */ - public function getPdoStatement() - { - return $this->_statement; - } - - /** * Prepares the SQL statement to be executed. * For complex SQL statement that is to be executed multiple times, * this may improve performance. @@ -184,18 +158,15 @@ class Command extends \yii\base\Component */ public function prepare() { - if ($this->_statement == null) - { - try - { - $this->_statement = $this->getConnection()->getPdoInstance()->prepare($this->getText()); + if ($this->pdoStatement == null) { + try { + $this->pdoStatement = $this->connection->pdo->prepare($this->getSql()); $this->_paramLog = array(); } - catch(Exception $e) - { - Yii::log('Error in preparing SQL: ' . $this->getText(), CLogger::LEVEL_ERROR, 'system.db.Command'); - $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; - throw new CDbException(Yii::t('yii', 'Command failed to prepare the SQL statement: {error}', + catch(Exception $e) { + Yii::log('Error in preparing SQL: ' . $this->getSql(), CLogger::LEVEL_ERROR, 'system.db.Command'); + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + throw new Exception('Unable to prepare the SQL statement: {error}', array('{error}' => $e->getMessage())), (int)$e->getCode(), $errorInfo); } } @@ -206,7 +177,7 @@ class Command extends \yii\base\Component */ public function cancel() { - $this->_statement = null; + $this->pdoStatement = null; } /** @@ -226,13 +197,13 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) - $this->_statement->bindParam($name, $value, $this->_connection->getPdoType(gettype($value))); + $this->pdoStatement->bindParam($name, $value, $this->connection->getPdoType(gettype($value))); elseif ($length === null) - $this->_statement->bindParam($name, $value, $dataType); + $this->pdoStatement->bindParam($name, $value, $dataType); elseif ($driverOptions === null) - $this->_statement->bindParam($name, $value, $dataType, $length); + $this->pdoStatement->bindParam($name, $value, $dataType, $length); else - $this->_statement->bindParam($name, $value, $dataType, $length, $driverOptions); + $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); $this->_paramLog[$name] =& $value; return $this; } @@ -252,9 +223,9 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) - $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); + $this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value))); else - $this->_statement->bindValue($name, $value, $dataType); + $this->pdoStatement->bindValue($name, $value, $dataType); $this->_paramLog[$name] = $value; return $this; } @@ -274,7 +245,7 @@ class Command extends \yii\base\Component $this->prepare(); foreach ($values as $name => $value) { - $this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value))); + $this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value))); $this->_paramLog[$name] = $value; } return $this; @@ -295,7 +266,7 @@ class Command extends \yii\base\Component */ public function execute($params = array()) { - if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) + if ($this->connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) { $p = array(); foreach ($pars as $name => $value) @@ -304,34 +275,34 @@ class Command extends \yii\base\Component } else $par = ''; - Yii::trace('Executing SQL: ' . $this->getText() . $par, 'system.db.Command'); + Yii::trace('Executing SQL: ' . $this->getSql() . $par, 'system.db.Command'); try { - if ($this->_connection->enableProfiling) - Yii::beginProfile('system.db.Command.execute(' . $this->getText() . ')', 'system.db.Command.execute'); + if ($this->connection->enableProfiling) + Yii::beginProfile('system.db.Command.execute(' . $this->getSql() . ')', 'system.db.Command.execute'); $this->prepare(); if ($params === array()) - $this->_statement->execute(); + $this->pdoStatement->execute(); else - $this->_statement->execute($params); - $n = $this->_statement->rowCount(); + $this->pdoStatement->execute($params); + $n = $this->pdoStatement->rowCount(); - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.Command.execute(' . $this->getText() . ')', 'system.db.Command.execute'); + if ($this->connection->enableProfiling) + Yii::endProfile('system.db.Command.execute(' . $this->getSql() . ')', 'system.db.Command.execute'); return $n; } catch(Exception $e) { - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.Command.execute(' . $this->getText() . ')', 'system.db.Command.execute'); + if ($this->connection->enableProfiling) + Yii::endProfile('system.db.Command.execute(' . $this->getSql() . ')', 'system.db.Command.execute'); $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; $message = $e->getMessage(); Yii::log(Yii::t('yii', 'Command::execute() failed: {error}. The SQL statement executed was: {sql}.', - array('{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.Command'); + array('{error}' => $message, '{sql}' => $this->getSql() . $par)), CLogger::LEVEL_ERROR, 'system.db.Command'); if (YII_DEBUG) - $message .= '. The SQL statement executed was: ' . $this->getText() . $par; + $message .= '. The SQL statement executed was: ' . $this->getSql() . $par; throw new CDbException(Yii::t('yii', 'Command failed to execute the SQL statement: {error}', array('{error}' => $message)), (int)$e->getCode(), $errorInfo); } @@ -351,7 +322,7 @@ class Command extends \yii\base\Component */ public function query($params = array()) { - return $this->queryInternal('', 0, $params); + return $this->queryInternal('', $params); } /** @@ -368,9 +339,9 @@ class Command extends \yii\base\Component * An empty array is returned if the query results in nothing. * @throws CException execution failed */ - public function queryAll($fetchAssociative = true, $params = array()) + public function queryAll($params = array(), $fetchMode = null) { - return $this->queryInternal('fetchAll', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); + return $this->queryInternal('fetchAll', $params, $fetchMode); } /** @@ -387,9 +358,9 @@ class Command extends \yii\base\Component * @return mixed the first row (in terms of an array) of the query result, false if no result. * @throws CException execution failed */ - public function queryRow($fetchAssociative = true, $params = array()) + public function queryRow($params = array(), $fetchMode = null) { - return $this->queryInternal('fetch', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params); + return $this->queryInternal('fetch', $params, $fetchMode); } /** @@ -407,11 +378,13 @@ class Command extends \yii\base\Component */ public function queryScalar($params = array()) { - $result = $this->queryInternal('fetchColumn', 0, $params); - if (is_resource($result) && get_resource_type($result) === 'stream') + $result = $this->queryInternal('fetchColumn', $params); + if (is_resource($result) && get_resource_type($result) === 'stream') { return stream_get_contents($result); - else + } + else { return $result; + } } /** @@ -429,7 +402,7 @@ class Command extends \yii\base\Component */ public function queryColumn($params = array()) { - return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN, $params); + return $this->queryInternal('fetchAll', $params, \PDO::FETCH_COLUMN); } /** @@ -443,11 +416,11 @@ class Command extends \yii\base\Component * This parameter has been available since version 1.0.10. * @return mixed the method execution result */ - private function queryInternal($method, $mode, $params = array()) + private function queryInternal($method, $params, $fetchMode = null) { $params = array_merge($this->params, $params); - if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) + if ($this->connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array()) { $p = array(); foreach ($pars as $name => $value) @@ -457,16 +430,16 @@ class Command extends \yii\base\Component else $par = ''; - Yii::trace('Querying SQL: ' . $this->getText() . $par, 'system.db.Command'); + Yii::trace('Querying SQL: ' . $this->getSql() . $par, 'system.db.Command'); - if ($this->_connection->queryCachingCount > 0 && $method !== '' - && $this->_connection->queryCachingDuration > 0 - && $this->_connection->queryCacheID !== false - && ($cache = Yii::app()->getComponent($this->_connection->queryCacheID)) !== null) + if ($this->connection->queryCachingCount > 0 && $method !== '' + && $this->connection->queryCachingDuration > 0 + && $this->connection->queryCacheID !== false + && ($cache = Yii::app()->getComponent($this->connection->queryCacheID)) !== null) { - $this->_connection->queryCachingCount--; - $cacheKey = 'yii:dbquery' . $this->_connection->connectionString . ':' . $this->_connection->username; - $cacheKey .= ':' . $this->getText() . ':' . serialize(array_merge($this->_paramLog, $params)); + $this->connection->queryCachingCount--; + $cacheKey = 'yii:dbquery' . $this->connection->connectionString . ':' . $this->connection->username; + $cacheKey .= ':' . $this->getSql() . ':' . serialize(array_merge($this->_paramLog, $params)); if (($result = $cache->get($cacheKey)) !== false) { Yii::trace('Query result found in cache', 'system.db.Command'); @@ -476,93 +449,50 @@ class Command extends \yii\base\Component try { - if ($this->_connection->enableProfiling) - Yii::beginProfile('system.db.Command.query(' . $this->getText() . $par . ')', 'system.db.Command.query'); + if ($this->connection->enableProfiling) + Yii::beginProfile('system.db.Command.query(' . $this->getSql() . $par . ')', 'system.db.Command.query'); $this->prepare(); if ($params === array()) - $this->_statement->execute(); + $this->pdoStatement->execute(); else - $this->_statement->execute($params); + $this->pdoStatement->execute($params); if ($method === '') - $result = new CDbDataReader($this); + $result = new DataReader($this); else { - $mode = (array)$mode; - $result = call_user_func_array(array($this->_statement, $method), $mode); - $this->_statement->closeCursor(); + if ($fetchMode === null) { + $fetchMode = $this->fetchMode; + } + $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode); + $this->pdoStatement->closeCursor(); } - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.Command.query(' . $this->getText() . $par . ')', 'system.db.Command.query'); + if ($this->connection->enableProfiling) + Yii::endProfile('system.db.Command.query(' . $this->getSql() . $par . ')', 'system.db.Command.query'); if (isset($cache, $cacheKey)) - $cache->set($cacheKey, $result, $this->_connection->queryCachingDuration, $this->_connection->queryCachingDependency); + $cache->set($cacheKey, $result, $this->connection->queryCachingDuration, $this->connection->queryCachingDependency); return $result; } catch(Exception $e) { - if ($this->_connection->enableProfiling) - Yii::endProfile('system.db.Command.query(' . $this->getText() . $par . ')', 'system.db.Command.query'); + if ($this->connection->enableProfiling) + Yii::endProfile('system.db.Command.query(' . $this->getSql() . $par . ')', 'system.db.Command.query'); $errorInfo = $e instanceof PDOException ? $e->errorInfo : null; $message = $e->getMessage(); Yii::log(Yii::t('yii', 'Command::{method}() failed: {error}. The SQL statement executed was: {sql}.', - array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.Command'); + array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getSql() . $par)), CLogger::LEVEL_ERROR, 'system.db.Command'); if (YII_DEBUG) - $message .= '. The SQL statement executed was: ' . $this->getText() . $par; + $message .= '. The SQL statement executed was: ' . $this->getSql() . $par; throw new CDbException(Yii::t('yii', 'Command failed to execute the SQL statement: {error}', array('{error}' => $message)), (int)$e->getCode(), $errorInfo); } } /** - * Builds a SQL SELECT statement from the given query specification. - * @param array $query the query specification in name-value pairs. The following - * query options are supported: {@link select}, {@link distinct}, {@link from}, - * {@link where}, {@link join}, {@link group}, {@link having}, {@link order}, - * {@link limit}, {@link offset} and {@link union}. - * @return string the SQL statement - * @since 1.1.6 - */ - public function buildQuery($query) - { - $sql = isset($query['distinct']) && $query['distinct'] ? 'SELECT DISTINCT' : 'SELECT'; - $sql .= ' ' . (isset($query['select']) ? $query['select'] : '*'); - - if (isset($query['from'])) - $sql .= "\nFROM " . $query['from']; - else - throw new CDbException(Yii::t('yii', 'The DB query must contain the "from" portion.')); - - if (isset($query['join'])) - $sql .= "\n" . (is_array($query['join']) ? implode("\n", $query['join']) : $query['join']); - - if (isset($query['where'])) - $sql .= "\nWHERE " . $query['where']; - - if (isset($query['group'])) - $sql .= "\nGROUP BY " . $query['group']; - - if (isset($query['having'])) - $sql .= "\nHAVING " . $query['having']; - - if (isset($query['order'])) - $sql .= "\nORDER BY " . $query['order']; - - $limit = isset($query['limit']) ? (int)$query['limit'] : -1; - $offset = isset($query['offset']) ? (int)$query['offset'] : -1; - if ($limit >= 0 || $offset > 0) - $sql = $this->_connection->getCommandBuilder()->applyLimit($sql, $limit, $offset); - - if (isset($query['union'])) - $sql .= "\nUNION (\n" . (is_array($query['union']) ? implode("\n) UNION (\n", $query['union']) : $query['union']) . ')'; - - return $sql; - } - - /** * Sets the SELECT part of the query. * @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns. * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). @@ -576,84 +506,22 @@ class Command extends \yii\base\Component */ public function select($columns = '*', $option = '') { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['select'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $column, $matches)) - $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' AS ' . $this->_connection->quoteColumnName($matches[2]); - else - $columns[$i] = $this->_connection->quoteColumnName($column); - } - } - $this->_query['select'] = implode(', ', $columns); - } - if ($option != '') - $this->_query['select'] = $option . ' ' . $this->_query['select']; + $this->query->select = $columns; + $this->query->selectOption = $option; return $this; } /** - * Returns the SELECT part in the query. - * @return string the SELECT part (without 'SELECT') in the query. - * @since 1.1.6 - */ - public function getSelect() - { - return isset($this->_query['select']) ? $this->_query['select'] : ''; - } - - /** - * Sets the SELECT part in the query. - * @param mixed $value the data to be selected. Please refer to {@link select()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setSelect($value) - { - $this->select($value); - } - - /** * Sets the SELECT part of the query with the DISTINCT flag turned on. * This is the same as {@link select} except that the DISTINCT flag is turned on. * @param mixed $columns the columns to be selected. See {@link select} for more details. * @return Command the command object itself * @since 1.1.6 */ - public function selectDistinct($columns = '*') + public function selectDistinct($columns = '*', $option = '') { - $this->_query['distinct'] = true; - return $this->select($columns); - } - - /** - * Returns a value indicating whether SELECT DISTINCT should be used. - * @return boolean a value indicating whether SELECT DISTINCT should be used. - * @since 1.1.6 - */ - public function getDistinct() - { - return isset($this->_query['distinct']) ? $this->_query['distinct'] : false; - } - - /** - * Sets a value indicating whether SELECT DISTINCT should be used. - * @param boolean $value a value indicating whether SELECT DISTINCT should be used. - * @since 1.1.6 - */ - public function setDistinct($value) - { - $this->_query['distinct'] = $value; + $this->query->distinct = true; + return $this->select($columns, $option); } /** @@ -668,49 +536,11 @@ class Command extends \yii\base\Component */ public function from($tables) { - if (is_string($tables) && strpos($tables, '(') !== false) - $this->_query['from'] = $tables; - else - { - if (!is_array($tables)) - $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY); - foreach ($tables as $i => $table) - { - if (strpos($table, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias - $tables[$i] = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); - else - $tables[$i] = $this->_connection->quoteTableName($table); - } - } - $this->_query['from'] = implode(', ', $tables); - } + $this->query->from = $tables; return $this; } /** - * Returns the FROM part in the query. - * @return string the FROM part (without 'FROM' ) in the query. - * @since 1.1.6 - */ - public function getFrom() - { - return isset($this->_query['from']) ? $this->_query['from'] : ''; - } - - /** - * Sets the FROM part in the query. - * @param mixed $value the tables to be selected from. Please refer to {@link from()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setFrom($value) - { - $this->from($value); - } - - /** * Sets the WHERE part of the query. * * The method requires a $conditions parameter, and optionally a $params parameter @@ -750,34 +580,12 @@ class Command extends \yii\base\Component */ public function where($conditions, $params = array()) { - $this->_query['where'] = $this->processConditions($conditions); - foreach ($params as $name => $value) - $this->params[$name] = $value; + $this->query->where = $conditions; + $this->query->addParams($params); return $this; } /** - * Returns the WHERE part in the query. - * @return string the WHERE part (without 'WHERE' ) in the query. - * @since 1.1.6 - */ - public function getWhere() - { - return isset($this->_query['where']) ? $this->_query['where'] : ''; - } - - /** - * Sets the WHERE part in the query. - * @param mixed $value the where part. Please refer to {@link where()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setWhere($value) - { - $this->where($value); - } - - /** * Appends an INNER JOIN part to the query. * @param string $table the table to be joined. * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u'). @@ -791,31 +599,7 @@ class Command extends \yii\base\Component */ public function join($table, $conditions, $params = array()) { - return $this->joinInternal('join', $table, $conditions, $params); - } - - /** - * Returns the join part in the query. - * @return mixed the join part in the query. This can be an array representing - * multiple join fragments, or a string representing a single jojin fragment. - * Each join fragment will contain the proper join operator (e.g. LEFT JOIN). - * @since 1.1.6 - */ - public function getJoin() - { - return isset($this->_query['join']) ? $this->_query['join'] : ''; - } - - /** - * Sets the join part in the query. - * @param mixed $value the join part in the query. This can be either a string or - * an array representing multiple join parts in the query. Each part must contain - * the proper join operator (e.g. 'LEFT JOIN tbl_profile ON tbl_user.id=tbl_profile.id') - * @since 1.1.6 - */ - public function setJoin($value) - { - $this->_query['join'] = $value; + return $this->joinInternal('JOIN', $table, $conditions, $params); } /** @@ -832,7 +616,7 @@ class Command extends \yii\base\Component */ public function leftJoin($table, $conditions, $params = array()) { - return $this->joinInternal('left join', $table, $conditions, $params); + return $this->joinInternal('LEFT JOIN', $table, $conditions, $params); } /** @@ -849,7 +633,7 @@ class Command extends \yii\base\Component */ public function rightJoin($table, $conditions, $params = array()) { - return $this->joinInternal('right join', $table, $conditions, $params); + return $this->joinInternal('RIGHT JOIN', $table, $conditions, $params); } /** @@ -864,7 +648,7 @@ class Command extends \yii\base\Component */ public function crossJoin($table) { - return $this->joinInternal('cross join', $table); + return $this->joinInternal('CROSS JOIN', $table); } /** @@ -879,7 +663,7 @@ class Command extends \yii\base\Component */ public function naturalJoin($table) { - return $this->joinInternal('natural join', $table); + return $this->joinInternal('NATURAL JOIN', $table); } /** @@ -891,48 +675,13 @@ class Command extends \yii\base\Component * @return Command the command object itself * @since 1.1.6 */ - public function group($columns) + public function groupBy($columns) { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['group'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - $columns[$i] = $this->_connection->quoteColumnName($column); - } - $this->_query['group'] = implode(', ', $columns); - } + $this->query->groupBy = $columns; return $this; } /** - * Returns the GROUP BY part in the query. - * @return string the GROUP BY part (without 'GROUP BY' ) in the query. - * @since 1.1.6 - */ - public function getGroup() - { - return isset($this->_query['group']) ? $this->_query['group'] : ''; - } - - /** - * Sets the GROUP BY part in the query. - * @param mixed $value the GROUP BY part. Please refer to {@link group()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setGroup($value) - { - $this->group($value); - } - - /** * Sets the HAVING part of the query. * @param mixed $conditions the conditions to be put after HAVING. * Please refer to {@link where} on how to specify conditions. @@ -942,34 +691,12 @@ class Command extends \yii\base\Component */ public function having($conditions, $params = array()) { - $this->_query['having'] = $this->processConditions($conditions); - foreach ($params as $name => $value) - $this->params[$name] = $value; + $this->query->having = $conditions; + $this->query->addParams($params); return $this; } /** - * Returns the HAVING part in the query. - * @return string the HAVING part (without 'HAVING' ) in the query. - * @since 1.1.6 - */ - public function getHaving() - { - return isset($this->_query['having']) ? $this->_query['having'] : ''; - } - - /** - * Sets the HAVING part in the query. - * @param mixed $value the HAVING part. Please refer to {@link having()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setHaving($value) - { - $this->having($value); - } - - /** * Sets the ORDER BY part of the query. * @param mixed $columns the columns (and the directions) to be ordered by. * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). @@ -978,89 +705,26 @@ class Command extends \yii\base\Component * @return Command the command object itself * @since 1.1.6 */ - public function order($columns) + public function orderBy($columns) { - if (is_string($columns) && strpos($columns, '(') !== false) - $this->_query['order'] = $columns; - else - { - if (!is_array($columns)) - $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); - foreach ($columns as $i => $column) - { - if (is_object($column)) - $columns[$i] = (string)$column; - elseif (strpos($column, '(') === false) - { - if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) - $columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' ' . strtoupper($matches[2]); - else - $columns[$i] = $this->_connection->quoteColumnName($column); - } - } - $this->_query['order'] = implode(', ', $columns); - } + $this->query->orderBy = $columns; return $this; } /** - * Returns the ORDER BY part in the query. - * @return string the ORDER BY part (without 'ORDER BY' ) in the query. - * @since 1.1.6 - */ - public function getOrder() - { - return isset($this->_query['order']) ? $this->_query['order'] : ''; - } - - /** - * Sets the ORDER BY part in the query. - * @param mixed $value the ORDER BY part. Please refer to {@link order()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setOrder($value) - { - $this->order($value); - } - - /** * Sets the LIMIT part of the query. * @param integer $limit the limit * @param integer $offset the offset * @return Command the command object itself * @since 1.1.6 */ - public function limit($limit, $offset = null) + public function limit($limit) { - $this->_query['limit'] = (int)$limit; - if ($offset !== null) - $this->offset($offset); + $this->query->limit = $limit; return $this; } /** - * Returns the LIMIT part in the query. - * @return string the LIMIT part (without 'LIMIT' ) in the query. - * @since 1.1.6 - */ - public function getLimit() - { - return isset($this->_query['limit']) ? $this->_query['limit'] : -1; - } - - /** - * Sets the LIMIT part in the query. - * @param integer $value the LIMIT part. Please refer to {@link limit()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setLimit($value) - { - $this->limit($value); - } - - /** * Sets the OFFSET part of the query. * @param integer $offset the offset * @return Command the command object itself @@ -1068,32 +732,11 @@ class Command extends \yii\base\Component */ public function offset($offset) { - $this->_query['offset'] = (int)$offset; + $this->query->offset = $offset; return $this; } /** - * Returns the OFFSET part in the query. - * @return string the OFFSET part (without 'OFFSET' ) in the query. - * @since 1.1.6 - */ - public function getOffset() - { - return isset($this->_query['offset']) ? $this->_query['offset'] : -1; - } - - /** - * Sets the OFFSET part in the query. - * @param integer $value the OFFSET part. Please refer to {@link offset()} for details - * on how to specify this parameter. - * @since 1.1.6 - */ - public function setOffset($value) - { - $this->offset($value); - } - - /** * Appends a SQL statement using UNION operator. * @param string $sql the SQL statement to be appended using UNION * @return Command the command object itself @@ -1101,34 +744,8 @@ class Command extends \yii\base\Component */ public function union($sql) { - if (isset($this->_query['union']) && is_string($this->_query['union'])) - $this->_query['union'] = array($this->_query['union']); - - $this->_query['union'][] = $sql; - - return $this; - } - - /** - * Returns the UNION part in the query. - * @return mixed the UNION part (without 'UNION' ) in the query. - * This can be either a string or an array representing multiple union parts. - * @since 1.1.6 - */ - public function getUnion() - { - return isset($this->_query['union']) ? $this->_query['union'] : ''; - } - - /** - * Sets the UNION part in the query. - * @param mixed $value the UNION part. This can be either a string or an array - * representing multiple SQL statements to be unioned together. - * @since 1.1.6 - */ - public function setUnion($value) - { - $this->_query['union'] = $value; + $this->query->union[] = $sql; + return $this->query; } /** @@ -1141,28 +758,8 @@ class Command extends \yii\base\Component */ public function insert($table, $columns) { - $params = array(); - $names = array(); - $placeholders = array(); - foreach ($columns as $name => $value) - { - $names[] = $this->_connection->quoteColumnName($name); - if ($value instanceof CDbExpression) - { - $placeholders[] = $value->expression; - foreach ($value->params as $n => $v) - $params[$n] = $v; - } - else - { - $placeholders[] = ':' . $name; - $params[':' . $name] = $value; - } - } - $sql = 'INSERT INTO ' . $this->_connection->quoteTableName($table) - . ' (' . implode(', ', $names) . ') VALUES (' - . implode(', ', $placeholders) . ')'; - return $this->setText($sql)->execute($params); + $sql = $this->connection->getQueryBuilder()->insert($table, $columns, $params); + return $this->setSql($sql)->execute($params); } /** @@ -1178,25 +775,8 @@ class Command extends \yii\base\Component */ public function update($table, $columns, $conditions = '', $params = array()) { - $lines = array(); - foreach ($columns as $name => $value) - { - if ($value instanceof CDbExpression) - { - $lines[] = $this->_connection->quoteColumnName($name) . '=' . $value->expression; - foreach ($value->params as $n => $v) - $params[$n] = $v; - } - else - { - $lines[] = $this->_connection->quoteColumnName($name) . '=:' . $name; - $params[':' . $name] = $value; - } - } - $sql = 'UPDATE ' . $this->_connection->quoteTableName($table) . ' SET ' . implode(', ', $lines); - if (($where = $this->processConditions($conditions)) != '') - $sql .= ' WHERE ' . $where; - return $this->setText($sql)->execute($params); + $sql = $this->connection->getQueryBuilder()->update($table, $columns, $conditions, $params); + return $this->setSql($sql)->execute($params); } /** @@ -1210,10 +790,8 @@ class Command extends \yii\base\Component */ public function delete($table, $conditions = '', $params = array()) { - $sql = 'DELETE FROM ' . $this->_connection->quoteTableName($table); - if (($where = $this->processConditions($conditions)) != '') - $sql .= ' WHERE ' . $where; - return $this->setText($sql)->execute($params); + $sql = $this->connection->getQueryBuilder()->delete($table, $conditions); + return $this->setSql($sql)->execute($params); } /** @@ -1235,7 +813,8 @@ class Command extends \yii\base\Component */ public function createTable($table, $columns, $options = null) { - return $this->setText($this->getConnection()->getSchema()->createTable($table, $columns, $options))->execute(); + $sql = $this->connection->getQueryBuilder()->createTable($table, $columns, $options); + return $this->setSql($sql)->execute(); } /** @@ -1247,7 +826,8 @@ class Command extends \yii\base\Component */ public function renameTable($table, $newName) { - return $this->setText($this->getConnection()->getSchema()->renameTable($table, $newName))->execute(); + $sql = $this->connection->getQueryBuilder()->renameTable($table, $newName); + return $this->setSql($sql)->execute(); } /** @@ -1258,7 +838,8 @@ class Command extends \yii\base\Component */ public function dropTable($table) { - return $this->setText($this->getConnection()->getSchema()->dropTable($table))->execute(); + $sql = $this->connection->getQueryBuilder()->dropTable($table); + return $this->setSql($sql)->execute(); } /** @@ -1269,11 +850,8 @@ class Command extends \yii\base\Component */ public function truncateTable($table) { - $schema = $this->getConnection()->getSchema(); - $n = $this->setText($schema->truncateTable($table))->execute(); - if (strncasecmp($this->getConnection()->getDriverName(), 'sqlite', 6) === 0) - $schema->resetSequence($schema->getTable($table)); - return $n; + $sql = $this->connection->getQueryBuilder()->truncateTable($table); + return $this->setSql($sql)->execute(); } /** @@ -1288,7 +866,8 @@ class Command extends \yii\base\Component */ public function addColumn($table, $column, $type) { - return $this->setText($this->getConnection()->getSchema()->addColumn($table, $column, $type))->execute(); + $sql = $this->connection->getQueryBuilder()->addColumn($table, $column, $type); + return $this->setSql($sql)->execute(); } /** @@ -1300,7 +879,8 @@ class Command extends \yii\base\Component */ public function dropColumn($table, $column) { - return $this->setText($this->getConnection()->getSchema()->dropColumn($table, $column))->execute(); + $sql = $this->connection->getQueryBuilder()->dropColumn($table, $column); + return $this->setSql($sql)->execute(); } /** @@ -1313,7 +893,8 @@ class Command extends \yii\base\Component */ public function renameColumn($table, $name, $newName) { - return $this->setText($this->getConnection()->getSchema()->renameColumn($table, $name, $newName))->execute(); + $sql = $this->connection->getQueryBuilder()->renameColumn($table, $name, $newName); + return $this->setSql($sql)->execute(); } /** @@ -1328,7 +909,8 @@ class Command extends \yii\base\Component */ public function alterColumn($table, $column, $type) { - return $this->setText($this->getConnection()->getSchema()->alterColumn($table, $column, $type))->execute(); + $sql = $this->connection->getQueryBuilder()->alterColumn($table, $column, $type); + return $this->setSql($sql)->execute(); } /** @@ -1346,7 +928,8 @@ class Command extends \yii\base\Component */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) { - return $this->setText($this->getConnection()->getSchema()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update))->execute(); + $sql = $this->connection->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update); + return $this->setSql($sql)->execute(); } /** @@ -1358,7 +941,8 @@ class Command extends \yii\base\Component */ public function dropForeignKey($name, $table) { - return $this->setText($this->getConnection()->getSchema()->dropForeignKey($name, $table))->execute(); + $sql = $this->connection->getQueryBuilder()->dropForeignKey($name, $table); + return $this->setSql($sql)->execute(); } /** @@ -1373,7 +957,8 @@ class Command extends \yii\base\Component */ public function createIndex($name, $table, $column, $unique = false) { - return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute(); + $sql = $this->connection->getQueryBuilder()->createIndex($name, $table, $column, $unique); + return $this->setSql($sql)->execute(); } /** @@ -1385,78 +970,8 @@ class Command extends \yii\base\Component */ public function dropIndex($name, $table) { - return $this->setText($this->getConnection()->getSchema()->dropIndex($name, $table))->execute(); - } - - /** - * Generates the condition string that will be put in the WHERE part - * @param mixed $conditions the conditions that will be put in the WHERE part. - * @return string the condition string to put in the WHERE part - */ - private function processConditions($conditions) - { - if (!is_array($conditions)) - return $conditions; - elseif ($conditions === array()) - return ''; - $n = count($conditions); - $operator = strtoupper($conditions[0]); - if ($operator === 'OR' || $operator === 'AND') - { - $parts = array(); - for ($i = 1;$i < $n;++$i) - { - $condition = $this->processConditions($conditions[$i]); - if ($condition !== '') - $parts[] = '(' . $condition . ')'; - } - return $parts === array() ? '' : implode(' ' . $operator . ' ', $parts); - } - - if (!isset($conditions[1], $conditions[2])) - return ''; - - $column = $conditions[1]; - if (strpos($column, '(') === false) - $column = $this->_connection->quoteColumnName($column); - - $values = $conditions[2]; - if (!is_array($values)) - $values = array($values); - - if ($operator === 'IN' || $operator === 'NOT IN') - { - if ($values === array()) - return $operator === 'IN' ? '0=1' : ''; - foreach ($values as $i => $value) - { - if (is_string($value)) - $values[$i] = $this->_connection->quoteValue($value); - else - $values[$i] = (string)$value; - } - return $column . ' ' . $operator . ' (' . implode(', ', $values) . ')'; - } - - if ($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE') - { - if ($values === array()) - return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : ''; - - if ($operator === 'LIKE' || $operator === 'NOT LIKE') - $andor = ' AND '; - else - { - $andor = ' OR '; - $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE'; - } - $expressions = array(); - foreach ($values as $value) - $expressions[] = $column . ' ' . $operator . ' ' . $this->_connection->quoteValue($value); - return implode($andor, $expressions); - } - - throw new CDbException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}' => $operator))); + $sql = $this->connection->getQueryBuilder()->dropIndex($name, $table); + return $this->setSql($sql)->execute(); } /** @@ -1474,25 +989,8 @@ class Command extends \yii\base\Component */ private function joinInternal($type, $table, $conditions = '', $params = array()) { - if (strpos($table, '(') === false) - { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias - $table = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]); - else - $table = $this->_connection->quoteTableName($table); - } - - $conditions = $this->processConditions($conditions); - if ($conditions != '') - $conditions = ' ON ' . $conditions; - - if (isset($this->_query['join']) && is_string($this->_query['join'])) - $this->_query['join'] = array($this->_query['join']); - - $this->_query['join'][] = strtoupper($type) . ' ' . $table . $conditions; - - foreach ($params as $name => $value) - $this->params[$name] = $value; + $this->query->join[] = array($type, $table, $conditions); + $this->query->addParams($params); return $this; } } diff --git a/framework/db/dao/Connection.php b/framework/db/dao/Connection.php index edea75b..2d4b8a7 100644 --- a/framework/db/dao/Connection.php +++ b/framework/db/dao/Connection.php @@ -460,6 +460,11 @@ class Connection extends \yii\base\ApplicationComponent } } + public function getQueryBuilder() + { + return $this->getSchema()->getQueryBuilder(); + } + /** * Returns the ID of the last inserted row or sequence value. * @param string $sequenceName name of the sequence object (required by some DBMS) diff --git a/framework/db/dao/DataReader.php b/framework/db/dao/DataReader.php index 663f526..d2228d8 100644 --- a/framework/db/dao/DataReader.php +++ b/framework/db/dao/DataReader.php @@ -8,6 +8,8 @@ * @license http://www.yiiframework.com/license/ */ +namespace yii\db\dao; + /** * DataReader represents a forward-only stream of rows from a query result set. * diff --git a/framework/db/dao/Query.php b/framework/db/dao/Query.php index 691dbda..cf96ad1 100644 --- a/framework/db/dao/Query.php +++ b/framework/db/dao/Query.php @@ -16,7 +16,7 @@ namespace yii\db\dao; * @author Qiang Xue * @since 2.0 */ -class Query extends CComponent +class Query extends \yii\base\Component { /** * @var mixed the columns being selected. This refers to the SELECT clause in an SQL @@ -72,7 +72,7 @@ class Query extends CComponent * @var array list of query parameter values indexed by parameter placeholders. * For example, array(':name'=>'Dan', ':age'=>31). */ - public $params; + public $params = array(); public $union; @@ -82,6 +82,18 @@ class Query extends CComponent return $connection->getQueryBuilder()->build($this); } + public function addParams($params) + { + foreach ($params as $name => $value) { + if (is_integer($name)) { + $this->params[] = $value; + } + else { + $this->params[$name] = $value; + } + } + } + /** * Appends a condition to the existing {@link condition}. * The new condition and the existing condition will be concatenated via the specified operator @@ -361,157 +373,6 @@ class Query extends CComponent } /** - * Merges with another criteria. - * In general, the merging makes the resulting criteria more restrictive. - * For example, if both criterias have conditions, they will be 'AND' together. - * Also, the criteria passed as the parameter takes precedence in case - * two options cannot be merged (e.g. LIMIT, OFFSET). - * @param Query $criteria the criteria to be merged with. - * @param boolean $useAnd whether to use 'AND' to merge condition and having options. - * If false, 'OR' will be used instead. Defaults to 'AND'. This parameter has been - * available since version 1.0.6. - * @since 1.0.5 - */ - public function mergeWith($criteria, $useAnd = true) - { - $and = $useAnd ? 'AND' : 'OR'; - if (is_array($criteria)) - $criteria = new self($criteria); - if ($this->select !== $criteria->select) - { - if ($this->select === '*') - $this->select = $criteria->select; - elseif ($criteria->select !== '*') - { - $select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select; - $select2 = is_string($criteria->select) ? preg_split('/\s*,\s*/', trim($criteria->select), -1, PREG_SPLIT_NO_EMPTY) : $criteria->select; - $this->select = array_merge($select1, array_diff($select2, $select1)); - } - } - - if ($this->condition !== $criteria->condition) - { - if ($this->condition === '') - $this->condition = $criteria->condition; - elseif ($criteria->condition !== '') - $this->condition = "( {$this->condition}) $and ( {$criteria->condition})"; - } - - if ($this->params !== $criteria->params) - $this->params = array_merge($this->params, $criteria->params); - - if ($criteria->limit > 0) - $this->limit = $criteria->limit; - - if ($criteria->offset >= 0) - $this->offset = $criteria->offset; - - if ($criteria->alias !== null) - $this->alias = $criteria->alias; - - if ($this->order !== $criteria->order) - { - if ($this->order === '') - $this->order = $criteria->order; - elseif ($criteria->order !== '') - $this->order = $criteria->order . ', ' . $this->order; - } - - if ($this->group !== $criteria->group) - { - if ($this->group === '') - $this->group = $criteria->group; - elseif ($criteria->group !== '') - $this->group .= ', ' . $criteria->group; - } - - if ($this->join !== $criteria->join) - { - if ($this->join === '') - $this->join = $criteria->join; - elseif ($criteria->join !== '') - $this->join .= ' ' . $criteria->join; - } - - if ($this->having !== $criteria->having) - { - if ($this->having === '') - $this->having = $criteria->having; - elseif ($criteria->having !== '') - $this->having = "( {$this->having}) $and ( {$criteria->having})"; - } - - if ($criteria->distinct > 0) - $this->distinct = $criteria->distinct; - - if ($criteria->together !== null) - $this->together = $criteria->together; - - if ($criteria->index !== null) - $this->index = $criteria->index; - - if (empty($this->scopes)) - $this->scopes = $criteria->scopes; - elseif (!empty($criteria->scopes)) - { - $scopes1 = (array)$this->scopes; - $scopes2 = (array)$criteria->scopes; - foreach ($scopes1 as $k => $v) - { - if (is_integer($k)) - $scopes[] = $v; - elseif (isset($scopes2[$k])) - $scopes[] = array($k => $v); - else - $scopes[$k] = $v; - } - foreach ($scopes2 as $k => $v) - { - if (is_integer($k)) - $scopes[] = $v; - elseif (isset($scopes1[$k])) - $scopes[] = array($k => $v); - else - $scopes[$k] = $v; - } - $this->scopes = $scopes; - } - - if (empty($this->with)) - $this->with = $criteria->with; - elseif (!empty($criteria->with)) - { - $this->with = (array)$this->with; - foreach ((array)$criteria->with as $k => $v) - { - if (is_integer($k)) - $this->with[] = $v; - elseif (isset($this->with[$k])) - { - $excludes = array(); - foreach (array('joinType', 'on') as $opt) - { - if (isset($this->with[$k][$opt])) - $excludes[$opt] = $this->with[$k][$opt]; - if (isset($v[$opt])) - $excludes[$opt] = ($opt === 'on' && isset($excludes[$opt]) && $v[$opt] !== $excludes[$opt]) ? - "($excludes[$opt]) AND $v[$opt]" : $v[$opt]; - unset($this->with[$k][$opt]); - unset($v[$opt]); - } - $this->with[$k] = new self($this->with[$k]); - $this->with[$k]->mergeWith($v, $useAnd); - $this->with[$k] = $this->with[$k]->toArray(); - if (count($excludes) !== 0) - $this->with[$k] = CMap::mergeArray($this->with[$k], $excludes); - } - else - $this->with[$k] = $v; - } - } - } - - /** * @return array the array representation of the criteria * @since 1.0.6 */ diff --git a/framework/db/dao/QueryBuilder.php b/framework/db/dao/QueryBuilder.php index c32a429..f26118b 100644 --- a/framework/db/dao/QueryBuilder.php +++ b/framework/db/dao/QueryBuilder.php @@ -42,7 +42,7 @@ class QueryBuilder extends \yii\base\Component public function __construct($schema) { - $this->connection = $schema->getDbConnection(); + $this->connection = $schema->connection; $this->schema = $schema; } @@ -59,8 +59,98 @@ class QueryBuilder extends \yii\base\Component $this->buildOrderBy($query), $this->buildLimit($query), ); + $sql = implode("\n", array_filter($clauses)); + if ($this->connection->tablePrefix !== null && strpos($sql, '{') !== false) { + $sql = preg_replace('/{{(.*?)}}/', $this->connection->tablePrefix . '\1', $sql); + } + return $sql; + } + + /** + * Creates and executes an INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column data (name=>value) to be inserted into the table. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function insert($table, $columns, &$params = array()) + { + $names = array(); + $placeholders = array(); + $count = 0; + foreach ($columns as $name => $value) { + $names[] = $this->schema->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++; + } + } + + return 'INSERT INTO ' . $this->schema->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. + * @param string $table the table to be updated. + * @param array $columns the column data (name=>value) to be updated. + * @param mixed $conditions the conditions that will be put in the WHERE part. Please + * refer to {@link where} on how to specify conditions. + * @param array $params the parameters to be bound to the query. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function update($table, $columns, $conditions = '', &$params = array()) + { + $lines = array(); + $count = 0; + foreach ($columns as $name => $value) { + if ($value instanceof Expression) { + $lines[] = $this->schema->quoteSimpleColumnName($name) . '=' . $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } + else { + $lines[] = $this->schema->quoteSimpleColumnName($name) . '=:p' . $count; + $params[':p' . $count] = $value; + $count++; + } + } + $sql = 'UPDATE ' . $this->schema->quoteTableName($table) . ' SET ' . implode(', ', $lines); + if (($where = $this->buildCondition($conditions)) != '') { + $sql .= ' WHERE ' . $where; + } + + return $sql; + } - return implode("\n", array_filter($clauses)); + /** + * Creates and executes a DELETE SQL statement. + * @param string $table the table where the data will be deleted from. + * @param mixed $conditions the conditions that will be put in the WHERE part. Please + * refer to {@link where} on how to specify conditions. + * @param array $params the parameters to be bound to the query. + * @return integer number of rows affected by the execution. + * @since 1.1.6 + */ + public function delete($table, $conditions = '') + { + $sql = 'DELETE FROM ' . $this->schema->quoteTableName($table); + if (($where = $this->buildCondition($conditions)) != '') { + $sql .= ' WHERE ' . $where; + } + return $sql; } /** @@ -101,7 +191,7 @@ class QueryBuilder extends \yii\base\Component */ public function renameTable($table, $newName) { - return 'RENAME TABLE ' . $this->quoteTableName($table) . ' TO ' . $this->quoteTableName($newName); + return 'RENAME TABLE ' . $this->schema->quoteTableName($table) . ' TO ' . $this->schema->quoteTableName($newName); } /** @@ -111,7 +201,7 @@ class QueryBuilder extends \yii\base\Component */ public function dropTable($table) { - return "DROP TABLE " . $this->quoteTableName($table); + return "DROP TABLE " . $this->schema->quoteTableName($table); } /** @@ -121,7 +211,7 @@ class QueryBuilder extends \yii\base\Component */ public function truncateTable($table) { - return "TRUNCATE TABLE " . $this->quoteTableName($table); + return "TRUNCATE TABLE " . $this->schema->quoteTableName($table); } /** @@ -136,8 +226,8 @@ class QueryBuilder extends \yii\base\Component */ public function addColumn($table, $column, $type) { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD ' . $this->quoteColumnName($column) . ' ' + return 'ALTER TABLE ' . $this->schema->quoteTableName($table) + . ' ADD ' . $this->schema->quoteColumnName($column) . ' ' . $this->getColumnType($type); } @@ -150,8 +240,8 @@ class QueryBuilder extends \yii\base\Component */ public function dropColumn($table, $column) { - return "ALTER TABLE " . $this->quoteTableName($table) - . " DROP COLUMN " . $this->quoteSimpleColumnName($column); + return "ALTER TABLE " . $this->schema->quoteTableName($table) + . " DROP COLUMN " . $this->schema->quoteSimpleColumnName($column); } /** @@ -164,9 +254,9 @@ class QueryBuilder extends \yii\base\Component */ public function renameColumn($table, $name, $newName) { - return "ALTER TABLE " . $this->quoteTableName($table) - . " RENAME COLUMN " . $this->quoteSimpleColumnName($name) - . " TO " . $this->quoteSimpleColumnName($newName); + return "ALTER TABLE " . $this->schema->quoteTableName($table) + . " RENAME COLUMN " . $this->schema->quoteSimpleColumnName($name) + . " TO " . $this->schema->quoteSimpleColumnName($newName); } /** @@ -180,9 +270,9 @@ class QueryBuilder extends \yii\base\Component */ public function alterColumn($table, $column, $type) { - return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE ' - . $this->quoteSimpleColumnName($column) . ' ' - . $this->quoteSimpleColumnName($column) . ' ' + return 'ALTER TABLE ' . $this->schema->quoteTableName($table) . ' CHANGE ' + . $this->schema->quoteSimpleColumnName($column) . ' ' + . $this->schema->quoteSimpleColumnName($column) . ' ' . $this->getColumnType($type); } @@ -202,14 +292,14 @@ class QueryBuilder extends \yii\base\Component { $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); foreach ($columns as $i => $col) - $columns[$i] = $this->quoteColumnName($col); + $columns[$i] = $this->schema->quoteColumnName($col); $refColumns = preg_split('/\s*,\s*/', $refColumns, -1, PREG_SPLIT_NO_EMPTY); foreach ($refColumns as $i => $col) - $refColumns[$i] = $this->quoteColumnName($col); - $sql = 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD CONSTRAINT ' . $this->quoteColumnName($name) + $refColumns[$i] = $this->schema->quoteColumnName($col); + $sql = 'ALTER TABLE ' . $this->schema->quoteTableName($table) + . ' ADD CONSTRAINT ' . $this->schema->quoteColumnName($name) . ' FOREIGN KEY (' . implode(', ', $columns) . ')' - . ' REFERENCES ' . $this->quoteTableName($refTable) + . ' REFERENCES ' . $this->schema->quoteTableName($refTable) . ' (' . implode(', ', $refColumns) . ')'; if ($delete !== null) $sql .= ' ON DELETE ' . $delete; @@ -226,8 +316,8 @@ class QueryBuilder extends \yii\base\Component */ public function dropForeignKey($name, $table) { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' DROP CONSTRAINT ' . $this->quoteColumnName($name); + return 'ALTER TABLE ' . $this->schema->quoteTableName($table) + . ' DROP CONSTRAINT ' . $this->schema->quoteColumnName($name); } /** @@ -248,11 +338,11 @@ class QueryBuilder extends \yii\base\Component if (strpos($col, '(') !== false) $cols[] = $col; else - $cols[] = $this->quoteColumnName($col); + $cols[] = $this->schema->quoteColumnName($col); } return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') - . $this->quoteTableName($name) . ' ON ' - . $this->quoteTableName($table) . ' (' . implode(', ', $cols) . ')'; + . $this->schema->quoteTableName($name) . ' ON ' + . $this->schema->quoteTableName($table) . ' (' . implode(', ', $cols) . ')'; } /** @@ -263,7 +353,7 @@ class QueryBuilder extends \yii\base\Component */ public function dropIndex($name, $table) { - return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table); + return 'DROP INDEX ' . $this->schema->quoteTableName($name) . ' ON ' . $this->schema->quoteTableName($table); } /** @@ -330,6 +420,9 @@ class QueryBuilder extends \yii\base\Component protected function buildSelect($query) { $select = $query->distinct ? 'SELECT DISTINCT' : 'SELECT'; + if ($query->selectOption != '') { + $select .= ' ' . $query->selectOption; + } $columns = $query->select; if (empty($columns)) { @@ -408,7 +501,7 @@ class QueryBuilder extends \yii\base\Component } $joins[$i] = strtoupper($join[0]) . ' ' . $table; if (isset($join[2])) { // join condition - $condition = $this->processCondition($join[2]); + $condition = $this->buildCondition($join[2]); $joins[$i] .= ' ON ' . $condition; } } @@ -423,7 +516,7 @@ class QueryBuilder extends \yii\base\Component protected function buildWhere($query) { - $where = $this->processConditions($query->where); + $where = $this->buildCondition($query->where); return empty($where) ? '' : 'WHERE ' . $where; } @@ -453,7 +546,7 @@ class QueryBuilder extends \yii\base\Component protected function buildHaving($query) { - $having = $this->processConditions($query->having); + $having = $this->buildCondition($query->having); return empty($having) ? '' : 'HAVING ' . $having; } @@ -515,7 +608,7 @@ class QueryBuilder extends \yii\base\Component return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)"; } - protected function processCondition($conditions) + protected function buildCondition($conditions) { if (!is_array($conditions)) { return $conditions; @@ -529,7 +622,7 @@ class QueryBuilder extends \yii\base\Component if ($operator === 'OR' || $operator === 'AND') { $parts = array(); for ($i = 1; $i < $n; ++$i) { - $condition = $this->processCondition($conditions[$i]); + $condition = $this->buildCondition($conditions[$i]); if ($condition !== '') { $parts[] = '(' . $condition . ')'; }