From fe90d4dda05debec54cd55d597e01c47bec1b980 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 6 Jan 2013 22:41:20 -0500 Subject: [PATCH] implemented auto-quoting for DB commands. --- framework/db/Command.php | 43 +++--- framework/db/Connection.php | 69 +++++---- framework/db/Driver.php | 114 +++++++++------ framework/db/QueryBuilder.php | 225 +++++++++++------------------ framework/db/mysql/QueryBuilder.php | 15 +- tests/unit/framework/db/CommandTest.php | 9 ++ tests/unit/framework/db/ConnectionTest.php | 12 +- 7 files changed, 234 insertions(+), 253 deletions(-) diff --git a/framework/db/Command.php b/framework/db/Command.php index 8ae7912..90d7764 100644 --- a/framework/db/Command.php +++ b/framework/db/Command.php @@ -62,21 +62,6 @@ class Command extends \yii\base\Component 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 */ @@ -88,14 +73,26 @@ class Command extends \yii\base\Component /** * Specifies the SQL statement to be executed. * Any previous execution will be terminated or cancelled. - * @param string $value the SQL statement to be set. + * @param string $sql the SQL statement to be set. * @return Command this command instance */ - public function setSql($value) + public function setSql($sql) { - $this->_sql = $value; - $this->_params = array(); - $this->cancel(); + 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); + } + $this->_sql = $sql; + $this->_params = array(); + $this->cancel(); + } return $this; } @@ -110,7 +107,7 @@ class Command extends \yii\base\Component public function prepare() { if ($this->pdoStatement == null) { - $sql = $this->connection->expandTablePrefix($this->getSql()); + $sql = $this->getSql(); try { $this->pdoStatement = $this->connection->pdo->prepare($sql); } catch (\Exception $e) { @@ -223,7 +220,7 @@ class Command extends \yii\base\Component */ public function execute($params = array()) { - $sql = $this->connection->expandTablePrefix($this->getSql()); + $sql = $this->getSql(); $this->_params = array_merge($this->_params, $params); if ($this->_params === array()) { $paramLog = ''; @@ -356,7 +353,7 @@ class Command extends \yii\base\Component private function queryInternal($method, $params, $fetchMode = null) { $db = $this->connection; - $sql = $db->expandTablePrefix($this->getSql()); + $sql = $this->getSql(); $this->_params = array_merge($this->_params, $params); if ($this->_params === array()) { $paramLog = ''; diff --git a/framework/db/Connection.php b/framework/db/Connection.php index 7cc4e1b..c254943 100644 --- a/framework/db/Connection.php +++ b/framework/db/Connection.php @@ -124,7 +124,7 @@ class Connection extends \yii\base\ApplicationComponent public $attributes; /** * @var \PDO the PHP PDO instance associated with this DB connection. - * This property is mainly managed by [[open]] and [[close]] methods. + * 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. */ @@ -132,7 +132,7 @@ class Connection extends \yii\base\ApplicationComponent /** * @var boolean whether to enable schema caching. * Note that in order to enable truly schema caching, a valid cache component as specified - * by [[schemaCacheID]] must be enabled and [[schemaCacheEnabled]] must be set true. + * by [[schemaCacheID]] must be enabled and [[enableSchemaCache]] must be set true. * @see schemaCacheDuration * @see schemaCacheExclude * @see schemaCacheID @@ -159,7 +159,7 @@ class Connection extends \yii\base\ApplicationComponent /** * @var boolean whether to enable query caching. * Note that in order to enable query caching, a valid cache component as specified - * by [[queryCacheID]] must be enabled and [[queryCacheEnabled]] must be set true. + * by [[queryCacheID]] must be enabled and [[enableQueryCache]] must be set true. * * Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on * and off query caching on the fly. @@ -215,16 +215,24 @@ class Connection extends \yii\base\ApplicationComponent */ 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'. + * @var string the common prefix or suffix for table names. If a table name is given + * as `{{%TableName}}`, then the percentage character `%` will be replaced with this + * property value. For example, `{{%post}}` becomes `{{tbl_post}}` if this property is + * set as `"tbl_"`. Note that this property is only effective when [[enableAutoQuoting]] + * is true. + * @see enableAutoQuoting */ public $tablePrefix; /** + * @var boolean whether to enable automatic quoting of table names and column names. + * Defaults to true. When this property is true, any token enclosed within double curly brackets + * (e.g. `{{post}}`) in a SQL statement will be treated as a table name and will be quoted + * accordingly when the SQL statement is executed; and any token enclosed within double square + * brackets (e.g. `[[name]]`) will be treated as a column name and quoted accordingly. + * @see tablePrefix + */ + public $enableAutoQuoting = true; + /** * @var array a list of SQL statements that should be executed right after the DB connection is established. */ public $initSQLs; @@ -411,7 +419,12 @@ class Connection extends \yii\base\ApplicationComponent public function createCommand($sql = null, $params = array()) { $this->open(); - return new Command($this, $sql, $params); + $command = new Command(array( + 'connection' => $this, + 'sql' => $sql, + )); + $command->bindValues($params); + return $command; } /** @@ -513,43 +526,27 @@ class Connection extends \yii\base\ApplicationComponent /** * Quotes a table name for use in a query. * If the table name contains schema prefix, the prefix will also be properly quoted. + * If the table name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. * @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) + public function quoteTableName($name) { - return $simple ? $this->getDriver()->quoteSimpleTableName($name) : $this->getDriver()->quoteTableName($name); + return $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. + * If the column name contains prefix, the prefix will also be properly quoted. + * If the column name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. * @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) + public function quoteColumnName($name) { - if ($this->tablePrefix !== null && strpos($sql, '{{') !== false) { - return preg_replace('/{{(.*?)}}/', $this->tablePrefix . '\1', $sql); - } else { - return $sql; - } + return $this->getDriver()->quoteColumnName($name); } /** diff --git a/framework/db/Driver.php b/framework/db/Driver.php index 4ad8d69..5dbe5fb 100644 --- a/framework/db/Driver.php +++ b/framework/db/Driver.php @@ -92,8 +92,7 @@ abstract class Driver extends \yii\base\Object } $db = $this->connection; - - $realName = $db->expandTablePrefix($name); + $realName = $this->getRealTableName($name); /** @var $cache \yii\caching\Cache */ if ($db->enableSchemaCache && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null && !in_array($name, $db->schemaCacheExclude, true)) { @@ -168,37 +167,57 @@ abstract class Driver extends \yii\base\Object /** * 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. + * This method cleans up all cached table schemas so that they can be re-created later + * to reflect the database schema change. */ - public function refresh($tableName = null) + public function refresh() { - $db = $this->connection; /** @var $cache \yii\caching\Cache */ - if ($db->enableSchemaCache && ($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]); + if ($this->connection->enableSchemaCache && ($cache = \Yii::$application->getComponent($this->connection->schemaCacheID)) !== null) { + foreach ($this->_tables as $name => $table) { + $cache->delete($this->getCacheKey($name)); } } + $this->_tables = array(); + } + + /** + * 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.'); } /** * Quotes a table name for use in a query. * If the table name contains schema prefix, the prefix will also be properly quoted. + * If the table name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. * @param string $name table name * @return string the properly quoted table name * @see quoteSimpleTableName */ public function quoteTableName($name) { + if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { + return $name; + } if (strpos($name, '.') === false) { return $this->quoteSimpleTableName($name); } @@ -211,25 +230,19 @@ abstract class Driver extends \yii\base\Object } /** - * 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. + * If the column name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. * @param string $name column name * @return string the properly quoted column name * @see quoteSimpleColumnName */ public function quoteColumnName($name) { + if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { + return $name; + } if (($pos = strrpos($name, '.')) !== false) { $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.'; $name = substr($name, $pos + 1); @@ -240,36 +253,43 @@ abstract class Driver extends \yii\base\Object } /** - * 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 + * Quotes a simple table name for use in a query. + * A simple table name should contain the table name only without any schema prefix. + * If the table name is already quoted, this method will do nothing. + * @param string $name table name + * @return string the properly quoted table name */ - public function quoteSimpleColumnName($name) + public function quoteSimpleTableName($name) { - return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; + return strpos($name, "'") !== false ? $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 + * Quotes a simple column name for use in a query. + * A simple column name should contain the column name only without any prefix. + * If the column name is already quoted or is the asterisk character '*', this method will do nothing. + * @param string $name column name + * @return string the properly quoted column name */ - public function createQueryBuilder() + public function quoteSimpleColumnName($name) { - return new QueryBuilder($this->connection); + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; } /** - * 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. + * Returns the real name of a table name. + * This method will strip off curly brackets from the given table name + * and replace the percentage character in the name with [[Connection::tablePrefix]]. + * @param string $name the table name to be converted + * @return string the real name of the given table name */ - protected function findTableNames($schema = '') + public function getRealTableName($name) { - throw new Exception(get_class($this) . 'does not support fetching all table names.'); + if ($this->connection->enableAutoQuoting && strpos($name, '{{') !== false) { + $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name); + return str_replace('%', $this->connection->tablePrefix, $name); + } else { + return $name; + } } } diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index d038076..86ec501 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -32,10 +32,6 @@ class QueryBuilder extends \yii\base\Object */ 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. @@ -99,7 +95,7 @@ class QueryBuilder extends \yii\base\Object $placeholders = array(); $count = 0; foreach ($columns as $name => $value) { - $names[] = $this->quoteColumnName($name); + $names[] = $this->connection->quoteColumnName($name); if ($value instanceof Expression) { $placeholders[] = $value->expression; foreach ($value->params as $n => $v) { @@ -112,7 +108,7 @@ class QueryBuilder extends \yii\base\Object } } - return 'INSERT INTO ' . $this->quoteTableName($table) + return 'INSERT INTO ' . $this->connection->quoteTableName($table) . ' (' . implode(', ', $names) . ') VALUES (' . implode(', ', $placeholders) . ')'; } @@ -144,17 +140,17 @@ class QueryBuilder extends \yii\base\Object $count = 0; foreach ($columns as $name => $value) { if ($value instanceof Expression) { - $lines[] = $this->quoteColumnName($name, true) . '=' . $value->expression; + $lines[] = $this->connection->quoteColumnName($name) . '=' . $value->expression; foreach ($value->params as $n => $v) { $params[$n] = $v; } } else { - $lines[] = $this->quoteColumnName($name, true) . '=:p' . $count; + $lines[] = $this->connection->quoteColumnName($name) . '=:p' . $count; $params[':p' . $count] = $value; $count++; } } - $sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines); + $sql = 'UPDATE ' . $this->connection->quoteTableName($table) . ' SET ' . implode(', ', $lines); if (($where = $this->buildCondition($condition)) !== '') { $sql .= ' WHERE ' . $where; } @@ -179,7 +175,7 @@ class QueryBuilder extends \yii\base\Object */ public function delete($table, $condition = '') { - $sql = 'DELETE FROM ' . $this->quoteTableName($table); + $sql = 'DELETE FROM ' . $this->connection->quoteTableName($table); if (($where = $this->buildCondition($condition)) !== '') { $sql .= ' WHERE ' . $where; } @@ -217,12 +213,12 @@ class QueryBuilder extends \yii\base\Object $cols = array(); foreach ($columns as $name => $type) { if (is_string($name)) { - $cols[] = "\t" . $this->quoteColumnName($name) . ' ' . $this->getColumnType($type); + $cols[] = "\t" . $this->connection->quoteColumnName($name) . ' ' . $this->getColumnType($type); } else { $cols[] = "\t" . $type; } } - $sql = "CREATE TABLE " . $this->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; + $sql = "CREATE TABLE " . $this->connection->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; return $options === null ? $sql : $sql . ' ' . $options; } @@ -234,7 +230,7 @@ class QueryBuilder extends \yii\base\Object */ public function renameTable($oldName, $newName) { - return 'RENAME TABLE ' . $this->quoteTableName($oldName) . ' TO ' . $this->quoteTableName($newName); + return 'RENAME TABLE ' . $this->connection->quoteTableName($oldName) . ' TO ' . $this->connection->quoteTableName($newName); } /** @@ -244,7 +240,7 @@ class QueryBuilder extends \yii\base\Object */ public function dropTable($table) { - return "DROP TABLE " . $this->quoteTableName($table); + return "DROP TABLE " . $this->connection->quoteTableName($table); } /** @@ -254,7 +250,7 @@ class QueryBuilder extends \yii\base\Object */ public function truncateTable($table) { - return "TRUNCATE TABLE " . $this->quoteTableName($table); + return "TRUNCATE TABLE " . $this->connection->quoteTableName($table); } /** @@ -268,8 +264,8 @@ class QueryBuilder extends \yii\base\Object */ public function addColumn($table, $column, $type) { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD ' . $this->quoteColumnName($column) . ' ' + return 'ALTER TABLE ' . $this->connection->quoteTableName($table) + . ' ADD ' . $this->connection->quoteColumnName($column) . ' ' . $this->getColumnType($type); } @@ -281,8 +277,8 @@ class QueryBuilder extends \yii\base\Object */ public function dropColumn($table, $column) { - return "ALTER TABLE " . $this->quoteTableName($table) - . " DROP COLUMN " . $this->quoteColumnName($column, true); + return "ALTER TABLE " . $this->connection->quoteTableName($table) + . " DROP COLUMN " . $this->connection->quoteColumnName($column); } /** @@ -294,9 +290,9 @@ class QueryBuilder extends \yii\base\Object */ public function renameColumn($table, $oldName, $newName) { - return "ALTER TABLE " . $this->quoteTableName($table) - . " RENAME COLUMN " . $this->quoteColumnName($oldName, true) - . " TO " . $this->quoteColumnName($newName, true); + return "ALTER TABLE " . $this->connection->quoteTableName($table) + . " RENAME COLUMN " . $this->connection->quoteColumnName($oldName) + . " TO " . $this->connection->quoteColumnName($newName); } /** @@ -311,9 +307,9 @@ class QueryBuilder extends \yii\base\Object */ public function alterColumn($table, $column, $type) { - return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE ' - . $this->quoteColumnName($column, true) . ' ' - . $this->quoteColumnName($column, true) . ' ' + return 'ALTER TABLE ' . $this->connection->quoteTableName($table) . ' CHANGE ' + . $this->connection->quoteColumnName($column) . ' ' + . $this->connection->quoteColumnName($column) . ' ' . $this->getColumnType($type); } @@ -333,10 +329,10 @@ class QueryBuilder extends \yii\base\Object */ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) { - $sql = 'ALTER TABLE ' . $this->quoteTableName($table) - . ' ADD CONSTRAINT ' . $this->quoteColumnName($name) + $sql = 'ALTER TABLE ' . $this->connection->quoteTableName($table) + . ' ADD CONSTRAINT ' . $this->connection->quoteColumnName($name) . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' - . ' REFERENCES ' . $this->quoteTableName($refTable) + . ' REFERENCES ' . $this->connection->quoteTableName($refTable) . ' (' . $this->buildColumns($refColumns) . ')'; if ($delete !== null) { $sql .= ' ON DELETE ' . $delete; @@ -355,8 +351,8 @@ class QueryBuilder extends \yii\base\Object */ public function dropForeignKey($name, $table) { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' DROP CONSTRAINT ' . $this->quoteColumnName($name); + return 'ALTER TABLE ' . $this->connection->quoteTableName($table) + . ' DROP CONSTRAINT ' . $this->connection->quoteColumnName($name); } /** @@ -372,8 +368,8 @@ class QueryBuilder extends \yii\base\Object public function createIndex($name, $table, $columns, $unique = false) { return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') - . $this->quoteTableName($name) . ' ON ' - . $this->quoteTableName($table) + . $this->connection->quoteTableName($name) . ' ON ' + . $this->connection->quoteTableName($table) . ' (' . $this->buildColumns($columns) . ')'; } @@ -385,7 +381,7 @@ class QueryBuilder extends \yii\base\Object */ public function dropIndex($name, $table) { - return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table); + return 'DROP INDEX ' . $this->connection->quoteTableName($name) . ' ON ' . $this->connection->quoteTableName($table); } /** @@ -500,7 +496,7 @@ class QueryBuilder extends \yii\base\Object $parts[] = $this->buildInCondition('in', array($column, $value)); } else { if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); + $column = $this->connection->quoteColumnName($column); } if ($value === null) { $parts[] = "$column IS NULL"; @@ -541,7 +537,7 @@ class QueryBuilder extends \yii\base\Object list($column, $value1, $value2) = $operands; if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); + $column = $this->connection->quoteColumnName($column); } $value1 = is_string($value1) ? $this->connection->quoteValue($value1) : (string)$value1; $value2 = is_string($value2) ? $this->connection->quoteValue($value2) : (string)$value2; @@ -581,7 +577,7 @@ class QueryBuilder extends \yii\base\Object } } if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); + $column = $this->connection->quoteColumnName($column); } if (count($values) > 1) { @@ -596,7 +592,7 @@ class QueryBuilder extends \yii\base\Object { foreach ($columns as $i => $column) { if (strpos($column, '(') === false) { - $columns[$i] = $this->quoteColumnName($column); + $columns[$i] = $this->connection->quoteColumnName($column); } } $vss = array(); @@ -636,7 +632,7 @@ class QueryBuilder extends \yii\base\Object } if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); + $column = $this->connection->quoteColumnName($column); } $parts = array(); @@ -664,24 +660,21 @@ class QueryBuilder extends \yii\base\Object 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); - } + 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); - } + } + 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] = $this->connection->quoteColumnName($matches[1]) . ' AS ' . $this->connection->quoteColumnName($matches[2]); + } else { + $columns[$i] = $this->connection->quoteColumnName($column); } } } @@ -703,22 +696,19 @@ class QueryBuilder extends \yii\base\Object 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); - } + 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); - } + } + foreach ($tables as $i => $table) { + if (strpos($table, '(') === false) { + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias + $tables[$i] = $this->connection->quoteTableName($matches[1]) . ' ' . $this->connection->quoteTableName($matches[2]); + } else { + $tables[$i] = $this->connection->quoteTableName($table); } } } @@ -747,12 +737,11 @@ class QueryBuilder extends \yii\base\Object 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 (strpos($table, '(') === false) { if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias - $table = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]); + $table = $this->connection->quoteTableName($matches[1]) . ' ' . $this->connection->quoteTableName($matches[2]); } else { - $table = $driver->quoteTableName($table); + $table = $this->connection->quoteTableName($table); } } $joins[$i] = $join[0] . ' ' . $table; @@ -813,24 +802,21 @@ class QueryBuilder extends \yii\base\Object 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); - } + 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); - } + } + 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]) . ' ' . $matches[2]; + } else { + $columns[$i] = $this->connection->quoteColumnName($column); } } } @@ -879,61 +865,26 @@ class QueryBuilder extends \yii\base\Object /** * 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); - } + 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); - } + } + foreach ($columns as $i => $column) { + if (is_object($column)) { + $columns[$i] = (string)$column; + } elseif (strpos($column, '(') === false) { + $columns[$i] = $this->connection->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/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php index 41cbc2c..7da3173 100644 --- a/framework/db/mysql/QueryBuilder.php +++ b/framework/db/mysql/QueryBuilder.php @@ -46,10 +46,11 @@ class QueryBuilder extends \yii\db\QueryBuilder * @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. + * @throws Exception */ public function renameColumn($table, $oldName, $newName) { - $quotedTable = $this->quoteTableName($table); + $quotedTable = $this->connection->quoteTableName($table); $row = $this->connection->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryRow(); if ($row === false) { throw new Exception("Unable to find '$oldName' in table '$table'."); @@ -64,16 +65,16 @@ class QueryBuilder extends \yii\db\QueryBuilder foreach ($matches[1] as $i => $c) { if ($c === $oldName) { return "ALTER TABLE $quotedTable CHANGE " - . $this->quoteColumnName($oldName, true) . ' ' - . $this->quoteColumnName($newName, true) . ' ' + . $this->connection->quoteColumnName($oldName) . ' ' + . $this->connection->quoteColumnName($newName) . ' ' . $matches[2][$i]; } } } // try to give back a SQL anyway return "ALTER TABLE $quotedTable CHANGE " - . $this->quoteColumnName($oldName, true) . ' ' - . $this->quoteColumnName($newName, true); + . $this->connection->quoteColumnName($oldName) . ' ' + . $this->connection->quoteColumnName($newName); } /** @@ -84,7 +85,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function dropForeignKey($name, $table) { - return 'ALTER TABLE ' . $this->quoteTableName($table) - . ' DROP FOREIGN KEY ' . $this->quoteColumnName($name); + return 'ALTER TABLE ' . $this->connection->quoteTableName($table) + . ' DROP FOREIGN KEY ' . $this->connection->quoteColumnName($name); } } diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index cd3c273..a55a016 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -36,6 +36,15 @@ class CommandTest extends \yiiunit\MysqlTestCase $this->assertEquals($sql2, $command->sql); } + function testAutoQuoting() + { + $db = $this->getConnection(false); + + $sql = 'SELECT [[id]], [[t.name]] FROM {{tbl_customer}} t'; + $command = $db->createCommand($sql); + $this->assertEquals("SELECT `id`, `t`.`name` FROM `tbl_customer` t", $command->sql); + } + function testPrepareCancel() { $db = $this->getConnection(false); diff --git a/tests/unit/framework/db/ConnectionTest.php b/tests/unit/framework/db/ConnectionTest.php index 7f3482c..e035eb2 100644 --- a/tests/unit/framework/db/ConnectionTest.php +++ b/tests/unit/framework/db/ConnectionTest.php @@ -49,7 +49,7 @@ class ConnectionTest extends \yiiunit\MysqlTestCase $connection = $this->getConnection(false); $this->assertEquals(123, $connection->quoteValue(123)); $this->assertEquals("'string'", $connection->quoteValue('string')); - $this->assertEquals("'It\'s interesting'", $connection->quoteValue("It's interesting")); + $this->assertEquals("'It\\'s interesting'", $connection->quoteValue("It's interesting")); } function testQuoteTableName() @@ -58,7 +58,10 @@ class ConnectionTest extends \yiiunit\MysqlTestCase $this->assertEquals('`table`', $connection->quoteTableName('table')); $this->assertEquals('`table`', $connection->quoteTableName('`table`')); $this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.table')); - $this->assertEquals('`schema.table`', $connection->quoteTableName('schema.table', true)); + $this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.`table`')); + $this->assertEquals('[[table]]', $connection->quoteTableName('[[table]]')); + $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); + $this->assertEquals('(table)', $connection->quoteTableName('(table)')); } function testQuoteColumnName() @@ -67,7 +70,10 @@ class ConnectionTest extends \yiiunit\MysqlTestCase $this->assertEquals('`column`', $connection->quoteColumnName('column')); $this->assertEquals('`column`', $connection->quoteColumnName('`column`')); $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.column')); - $this->assertEquals('`table.column`', $connection->quoteColumnName('table.column', true)); + $this->assertEquals('`table`.`column`', $connection->quoteColumnName('table.`column`')); + $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); } function testGetPdoType()