QueryBuilder.php 30 KB
Newer Older
w  
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10 11 12
<?php
/**
 * This file contains the Command class.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\db\dao;

w  
Qiang Xue committed
13 14
use yii\db\Exception;

w  
Qiang Xue committed
15
/**
Qiang Xue committed
16
 * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[BaseQuery]] object.
w  
Qiang Xue committed
17
 *
Qiang Xue committed
18
 * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE,
Qiang Xue committed
19 20
 * from a [[Query]] object.
 *
w  
Qiang Xue committed
21 22 23
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
Qiang Xue committed
24
class QueryBuilder extends \yii\base\Object
w  
Qiang Xue committed
25
{
Qiang Xue committed
26 27 28
	/**
	 * @var Connection the database connection.
	 */
w  
Qiang Xue committed
29
	public $connection;
Qiang Xue committed
30 31 32 33 34
	/**
	 * @var string the separator between different fragments of a SQL statement.
	 * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
	 */
	public $separator = " ";
Qiang Xue committed
35 36 37 38
	/**
	 * @var boolean whether to automatically quote table and column names when generating SQL statements.
	 */
	public $autoQuote = true;
Qiang Xue committed
39 40 41 42 43 44 45
	/**
	 * @var array the abstract column types mapped to physical column types.
	 * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
	 * Child classes should override this property to declare supported type mappings.
	 */
	public $typeMap = array();
	/**
Qiang Xue committed
46
	 * @var Query the Query object that is currently being processed by the query builder to generate a SQL statement.
Qiang Xue committed
47 48
	 */
	public $query;
w  
Qiang Xue committed
49

Qiang Xue committed
50 51
	/**
	 * Constructor.
Qiang Xue committed
52
	 * @param Connection $connection the database connection.
Qiang Xue committed
53
	 */
Qiang Xue committed
54
	public function __construct($connection)
w  
Qiang Xue committed
55
	{
Qiang Xue committed
56
		$this->connection = $connection;
w  
Qiang Xue committed
57 58
	}

Qiang Xue committed
59
	/**
Qiang Xue committed
60 61
	 * Generates a SELECT SQL statement from a [[BaseQuery]] object.
	 * @param BaseQuery $query the [[Query]] object from which the SQL statement will be generated
Qiang Xue committed
62
	 * @return string the generated SQL statement
Qiang Xue committed
63
	 */
w  
Qiang Xue committed
64 65
	public function build($query)
	{
Qiang Xue committed
66
		$clauses = array(
Qiang Xue committed
67 68 69 70 71 72 73 74 75
			$this->buildSelect($query->select, $query->distinct, $query->selectOption),
			$this->buildFrom($query->from),
			$this->buildJoin($query->join),
			$this->buildWhere($query->where),
			$this->buildGroupBy($query->groupBy),
			$this->buildHaving($query->having),
			$this->buildUnion($query->union),
			$this->buildOrderBy($query->orderBy),
			$this->buildLimit($query->limit, $query->offset),
Qiang Xue committed
76 77
		);
		return implode($this->separator, array_filter($clauses));
w  
Qiang Xue committed
78 79 80 81 82
	}

	/**
	 * Creates and executes an INSERT SQL statement.
	 * The method will properly escape the column names, and bind the values to be inserted.
Qiang Xue committed
83 84 85 86
	 * For example,
	 *
	 * ~~~
	 * $sql = $queryBuilder->insert('tbl_user', array(
Qiang Xue committed
87 88
	 *	 'name' => 'Sam',
	 *	 'age' => 30,
Qiang Xue committed
89
	 * ));
Qiang Xue committed
90 91
	 * ~~~
	 *
w  
Qiang Xue committed
92 93 94 95
	 * @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.
	 */
Qiang Xue committed
96
	public function insert($table, $columns)
w  
Qiang Xue committed
97 98 99 100
	{
		$names = array();
		$placeholders = array();
		$count = 0;
Qiang Xue committed
101
		$params = array();
w  
Qiang Xue committed
102
		foreach ($columns as $name => $value) {
Qiang Xue committed
103
			$names[] = $this->quoteColumnName($name);
w  
Qiang Xue committed
104 105 106 107 108
			if ($value instanceof Expression) {
				$placeholders[] = $value->expression;
				foreach ($value->params as $n => $v) {
					$params[$n] = $v;
				}
Qiang Xue committed
109
			} else {
w  
Qiang Xue committed
110 111 112 113 114
				$placeholders[] = ':p' . $count;
				$params[':p' . $count] = $value;
				$count++;
			}
		}
Qiang Xue committed
115
		if ($this->query instanceof BaseQuery) {
Qiang Xue committed
116
			$this->query->addParams($params);
Qiang Xue committed
117
		}
w  
Qiang Xue committed
118

Qiang Xue committed
119
		return 'INSERT INTO ' . $this->quoteTableName($table)
w  
Qiang Xue committed
120 121 122 123 124 125 126
			. ' (' . 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.
Qiang Xue committed
127 128 129 130 131
	 * For example,
	 *
	 * ~~~
	 * $params = array();
	 * $sql = $queryBuilder->update('tbl_user', array(
Qiang Xue committed
132
	 *	 'status' => 1,
Qiang Xue committed
133 134 135
	 * ), 'age > 30', $params);
	 * ~~~
	 *
w  
Qiang Xue committed
136 137
	 * @param string $table the table to be updated.
	 * @param array $columns the column data (name=>value) to be updated.
Qiang Xue committed
138 139
	 * @param mixed $condition the condition that will be put in the WHERE part. Please
	 * refer to [[Query::where()]] on how to specify condition.
Qiang Xue committed
140
	 * @param array $params the parameters to be bound to the query.
w  
Qiang Xue committed
141 142
	 * @return integer number of rows affected by the execution.
	 */
Qiang Xue committed
143
	public function update($table, $columns, $condition = '', $params = array())
w  
Qiang Xue committed
144 145 146 147 148
	{
		$lines = array();
		$count = 0;
		foreach ($columns as $name => $value) {
			if ($value instanceof Expression) {
Qiang Xue committed
149
				$lines[] = $this->quoteColumnName($name, true) . '=' . $value->expression;
w  
Qiang Xue committed
150 151 152
				foreach ($value->params as $n => $v) {
					$params[$n] = $v;
				}
Qiang Xue committed
153
			} else {
Qiang Xue committed
154
				$lines[] = $this->quoteColumnName($name, true) . '=:p' . $count;
w  
Qiang Xue committed
155 156 157 158
				$params[':p' . $count] = $value;
				$count++;
			}
		}
Qiang Xue committed
159
		if ($this->query instanceof BaseQuery) {
Qiang Xue committed
160
			$this->query->addParams($params);
Qiang Xue committed
161
		}
Qiang Xue committed
162
		$sql = 'UPDATE ' . $this->quoteTableName($table) . ' SET ' . implode(', ', $lines);
Qiang Xue committed
163
		if (($where = $this->buildCondition($condition)) !== '') {
w  
Qiang Xue committed
164 165
			$sql .= ' WHERE ' . $where;
		}
w  
Qiang Xue committed
166

w  
Qiang Xue committed
167 168 169 170 171
		return $sql;
	}

	/**
	 * Creates and executes a DELETE SQL statement.
Qiang Xue committed
172 173 174 175 176 177
	 * For example,
	 *
	 * ~~~
	 * $sql = $queryBuilder->delete('tbl_user', 'status = 0');
	 * ~~~
	 *
w  
Qiang Xue committed
178
	 * @param string $table the table where the data will be deleted from.
Qiang Xue committed
179 180
	 * @param mixed $condition the condition that will be put in the WHERE part. Please
	 * refer to [[Query::where()]] on how to specify condition.
Qiang Xue committed
181
	 * @param array $params the parameters to be bound to the query.
w  
Qiang Xue committed
182 183
	 * @return integer number of rows affected by the execution.
	 */
Qiang Xue committed
184
	public function delete($table, $condition = '', $params = array())
w  
Qiang Xue committed
185
	{
Qiang Xue committed
186
		$sql = 'DELETE FROM ' . $this->quoteTableName($table);
Qiang Xue committed
187
		if (($where = $this->buildCondition($condition)) !== '') {
w  
Qiang Xue committed
188 189
			$sql .= ' WHERE ' . $where;
		}
Qiang Xue committed
190 191 192
		if ($params !== array() && $this->query instanceof BaseQuery) {
			$this->query->addParams($params);
		}
w  
Qiang Xue committed
193
		return $sql;
w  
Qiang Xue committed
194 195
	}

w  
Qiang Xue committed
196 197 198 199 200 201
	/**
	 * Builds a SQL statement for creating a new DB table.
	 *
	 * The columns in the new  table should be specified as name-definition pairs (e.g. 'name'=>'string'),
	 * where name stands for a column name which will be properly quoted by the method, and definition
	 * stands for the column type which can contain an abstract DB type.
Qiang Xue committed
202
	 * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
w  
Qiang Xue committed
203 204 205 206
	 *
	 * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
	 * inserted into the generated SQL.
	 *
Qiang Xue committed
207 208 209 210
	 * For example,
	 *
	 * ~~~
	 * $sql = $queryBuilder->createTable('tbl_user', array(
Qiang Xue committed
211 212 213
	 *	 'id' => 'pk',
	 *	 'name' => 'string',
	 *	 'age' => 'integer',
Qiang Xue committed
214 215 216
	 * ));
	 * ~~~
	 *
w  
Qiang Xue committed
217 218 219 220 221 222 223 224
	 * @param string $table the name of the table to be created. The name will be properly quoted by the method.
	 * @param array $columns the columns (name=>definition) in the new table.
	 * @param string $options additional SQL fragment that will be appended to the generated SQL.
	 * @return string the SQL statement for creating a new DB table.
	 */
	public function createTable($table, $columns, $options = null)
	{
		$cols = array();
w  
Qiang Xue committed
225 226
		foreach ($columns as $name => $type) {
			if (is_string($name)) {
Qiang Xue committed
227 228
				$cols[] = "\t" . $this->quoteColumnName($name) . ' ' . $this->getColumnType($type);
			} else {
w  
Qiang Xue committed
229
				$cols[] = "\t" . $type;
Qiang Xue committed
230
			}
w  
Qiang Xue committed
231
		}
Qiang Xue committed
232
		$sql = "CREATE TABLE " . $this->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
w  
Qiang Xue committed
233 234 235 236 237
		return $options === null ? $sql : $sql . ' ' . $options;
	}

	/**
	 * Builds a SQL statement for renaming a DB table.
Qiang Xue committed
238
	 * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
w  
Qiang Xue committed
239 240 241
	 * @param string $newName the new table name. The name will be properly quoted by the method.
	 * @return string the SQL statement for renaming a DB table.
	 */
Qiang Xue committed
242
	public function renameTable($oldName, $newName)
w  
Qiang Xue committed
243
	{
Qiang Xue committed
244
		return 'RENAME TABLE ' . $this->quoteTableName($oldName) . ' TO ' . $this->quoteTableName($newName);
w  
Qiang Xue committed
245 246 247 248 249 250 251 252 253
	}

	/**
	 * Builds a SQL statement for dropping a DB table.
	 * @param string $table the table to be dropped. The name will be properly quoted by the method.
	 * @return string the SQL statement for dropping a DB table.
	 */
	public function dropTable($table)
	{
Qiang Xue committed
254
		return "DROP TABLE " . $this->quoteTableName($table);
w  
Qiang Xue committed
255 256 257 258 259 260 261 262
	}

	/**
	 * Builds a SQL statement for truncating a DB table.
	 * @param string $table the table to be truncated. The name will be properly quoted by the method.
	 * @return string the SQL statement for truncating a DB table.
	 */
	public function truncateTable($table)
w  
Qiang Xue committed
263
	{
Qiang Xue committed
264
		return "TRUNCATE TABLE " . $this->quoteTableName($table);
w  
Qiang Xue committed
265 266 267 268 269 270
	}

	/**
	 * Builds a SQL statement for adding a new DB column.
	 * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
	 * @param string $column the name of the new column. The name will be properly quoted by the method.
Qiang Xue committed
271
	 * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
w  
Qiang Xue committed
272 273 274 275 276 277
	 * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
	 * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
	 * @return string the SQL statement for adding a new column.
	 */
	public function addColumn($table, $column, $type)
	{
Qiang Xue committed
278 279
		return 'ALTER TABLE ' . $this->quoteTableName($table)
			. ' ADD ' . $this->quoteColumnName($column) . ' '
w  
Qiang Xue committed
280 281
			. $this->getColumnType($type);
	}
w  
Qiang Xue committed
282

w  
Qiang Xue committed
283 284 285 286 287 288 289 290
	/**
	 * Builds a SQL statement for dropping a DB column.
	 * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
	 * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
	 * @return string the SQL statement for dropping a DB column.
	 */
	public function dropColumn($table, $column)
	{
Qiang Xue committed
291 292
		return "ALTER TABLE " . $this->quoteTableName($table)
			. " DROP COLUMN " . $this->quoteColumnName($column, true);
w  
Qiang Xue committed
293 294 295 296 297
	}

	/**
	 * Builds a SQL statement for renaming a column.
	 * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
Qiang Xue committed
298
	 * @param string $oldName the old name of the column. The name will be properly quoted by the method.
w  
Qiang Xue committed
299 300 301
	 * @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.
	 */
Qiang Xue committed
302
	public function renameColumn($table, $oldName, $newName)
w  
Qiang Xue committed
303
	{
Qiang Xue committed
304 305 306
		return "ALTER TABLE " . $this->quoteTableName($table)
			. " RENAME COLUMN " . $this->quoteColumnName($oldName, true)
			. " TO " . $this->quoteColumnName($newName, true);
w  
Qiang Xue committed
307 308 309 310 311 312
	}

	/**
	 * Builds a SQL statement for changing the definition of a column.
	 * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
	 * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
Qiang Xue committed
313 314 315 316
	 * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
	 * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
	 * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
	 * will become 'varchar(255) not null'.
w  
Qiang Xue committed
317 318 319 320
	 * @return string the SQL statement for changing the definition of a column.
	 */
	public function alterColumn($table, $column, $type)
	{
Qiang Xue committed
321 322 323
		return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE '
			. $this->quoteColumnName($column, true) . ' '
			. $this->quoteColumnName($column, true) . ' '
w  
Qiang Xue committed
324 325 326 327 328 329 330 331
			. $this->getColumnType($type);
	}

	/**
	 * Builds a SQL statement for adding a foreign key constraint to an existing table.
	 * The method will properly quote the table and column names.
	 * @param string $name the name of the foreign key constraint.
	 * @param string $table the table that the foreign key constraint will be added to.
Qiang Xue committed
332 333
	 * @param string|array $columns the name of the column to that the constraint will be added on.
	 * If there are multiple columns, separate them with commas or use an array to represent them.
w  
Qiang Xue committed
334
	 * @param string $refTable the table that the foreign key references to.
Qiang Xue committed
335 336
	 * @param string|array $refColumns the name of the column that the foreign key references to.
	 * If there are multiple columns, separate them with commas or use an array to represent them.
w  
Qiang Xue committed
337 338 339 340 341 342
	 * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
	 * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
	 * @return string the SQL statement for adding a foreign key constraint to an existing table.
	 */
	public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
	{
Qiang Xue committed
343 344 345 346 347
		$sql = 'ALTER TABLE ' . $this->quoteTableName($table)
			. ' ADD CONSTRAINT ' . $this->quoteColumnName($name)
			. ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
			. ' REFERENCES ' . $this->quoteTableName($refTable)
			. ' (' . $this->buildColumns($refColumns) . ')';
Qiang Xue committed
348
		if ($delete !== null) {
w  
Qiang Xue committed
349
			$sql .= ' ON DELETE ' . $delete;
Qiang Xue committed
350 351
		}
		if ($update !== null) {
w  
Qiang Xue committed
352
			$sql .= ' ON UPDATE ' . $update;
Qiang Xue committed
353
		}
w  
Qiang Xue committed
354 355 356 357 358 359 360 361 362 363 364
		return $sql;
	}

	/**
	 * Builds a SQL statement for dropping a foreign key constraint.
	 * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
	 * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
	 * @return string the SQL statement for dropping a foreign key constraint.
	 */
	public function dropForeignKey($name, $table)
	{
Qiang Xue committed
365 366
		return 'ALTER TABLE ' . $this->quoteTableName($table)
			. ' DROP CONSTRAINT ' . $this->quoteColumnName($name);
w  
Qiang Xue committed
367 368 369 370 371 372
	}

	/**
	 * Builds a SQL statement for creating a new index.
	 * @param string $name the name of the index. The name will be properly quoted by the method.
	 * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
Qiang Xue committed
373 374 375
	 * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
	 * separate them with commas or use an array to represent them. Each column name will be properly quoted
	 * by the method, unless a parenthesis is found in the name.
w  
Qiang Xue committed
376 377 378
	 * @param boolean $unique whether to add UNIQUE constraint on the created index.
	 * @return string the SQL statement for creating a new index.
	 */
Qiang Xue committed
379
	public function createIndex($name, $table, $columns, $unique = false)
w  
Qiang Xue committed
380 381
	{
		return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
Qiang Xue committed
382 383 384
			. $this->quoteTableName($name) . ' ON '
			. $this->quoteTableName($table)
			. ' (' . $this->buildColumns($columns) . ')';
w  
Qiang Xue committed
385 386 387 388 389 390 391 392 393 394
	}

	/**
	 * Builds a SQL statement for dropping an index.
	 * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
	 * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
	 * @return string the SQL statement for dropping an index.
	 */
	public function dropIndex($name, $table)
	{
Qiang Xue committed
395
		return 'DROP INDEX ' . $this->quoteTableName($name) . ' ON ' . $this->quoteTableName($table);
w  
Qiang Xue committed
396 397
	}

w  
Qiang Xue committed
398 399 400 401
	/**
	 * Resets the sequence value of a table's primary key.
	 * The sequence will be reset such that the primary key of the next new row inserted
	 * will have the specified value or 1.
Qiang Xue committed
402
	 * @param string $table the table schema whose primary key sequence will be reset
w  
Qiang Xue committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
	 * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
	 * the next new row's primary key will have a value 1.
	 */
	public function resetSequence($table, $value = null)
	{
	}

	/**
	 * Enables or disables integrity check.
	 * @param boolean $check whether to turn on or off the integrity check.
	 * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
	 */
	public function checkIntegrity($check = true, $schema = '')
	{
	}

	/**
	 * Converts an abstract column type into a physical column type.
Qiang Xue committed
421
	 * The conversion is done using the type map specified in [[typeMap]].
Qiang Xue committed
422
	 * The following abstract column types are supported (using MySQL as an example to explain the corresponding
w  
Qiang Xue committed
423
	 * physical types):
Qiang Xue committed
424
	 *
Qiang Xue committed
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
	 * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
	 * - `string`: string type, will be converted into "varchar(255)"
	 * - `text`: a long string type, will be converted into "text"
	 * - `smallint`: a small integer type, will be converted into "smallint(6)"
	 * - `integer`: integer type, will be converted into "int(11)"
	 * - `bigint`: a big integer type, will be converted into "bigint(20)"
	 * - `boolean`: boolean type, will be converted into "tinyint(1)"
	 * - `float``: float number type, will be converted into "float"
	 * - `decimal`: decimal number type, will be converted into "decimal"
	 * - `datetime`: datetime type, will be converted into "datetime"
	 * - `timestamp`: timestamp type, will be converted into "timestamp"
	 * - `time`: time type, will be converted into "time"
	 * - `date`: date type, will be converted into "date"
	 * - `money`: money type, will be converted into "decimal(19,4)"
	 * - `binary`: binary data type, will be converted into "blob"
w  
Qiang Xue committed
440 441
	 *
	 * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
Qiang Xue committed
442
	 * the first part will be converted, and the rest of the parts will be appended to the converted result.
w  
Qiang Xue committed
443
	 * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
Qiang Xue committed
444 445
	 *
	 * If a type cannot be found in [[typeMap]], it will be returned without any change.
w  
Qiang Xue committed
446 447 448
	 * @param string $type abstract column type
	 * @return string physical column type.
	 */
Qiang Xue committed
449 450
	public function getColumnType($type)
	{
w  
Qiang Xue committed
451 452
		if (isset($this->typeMap[$type])) {
			return $this->typeMap[$type];
Qiang Xue committed
453 454 455 456 457 458 459 460 461
		} elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
			if (isset($this->typeMap[$matches[0]])) {
				return preg_replace('/^\w+/', $this->typeMap[$matches[0]], $type);
			}
		}
		return $type;
	}

	/**
Qiang Xue committed
462
	 * Parses the condition specification and generates the corresponding SQL expression.
Qiang Xue committed
463
	 * @param string|array $condition the condition specification. Please refer to [[BaseQuery::where()]]
Qiang Xue committed
464 465 466
	 * on how to specify a condition.
	 * @return string the generated SQL expression
	 * @throws \yii\db\Exception if the condition is in bad format
Qiang Xue committed
467 468 469
	 */
	public function buildCondition($condition)
	{
Qiang Xue committed
470 471 472 473 474 475 476 477 478 479 480 481 482
		static $builders = array(
			'and' => 'buildAndCondition',
			'or' => 'buildAndCondition',
			'between' => 'buildBetweenCondition',
			'not between' => 'buildBetweenCondition',
			'in' => 'buildInCondition',
			'not in' => 'buildInCondition',
			'like' => 'buildLikeCondition',
			'not like' => 'buildLikeCondition',
			'or like' => 'buildLikeCondition',
			'or not like' => 'buildLikeCondition',
		);

Qiang Xue committed
483
		if (!is_array($condition)) {
Qiang Xue committed
484
			return (string)$condition;
Qiang Xue committed
485 486 487
		} elseif ($condition === array()) {
			return '';
		}
Qiang Xue committed
488 489 490 491 492 493 494 495 496 497 498 499 500
		if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
			$operator = $condition[0];
			if (isset($builders[$operator])) {
				$method = $builders[$operator];
				array_shift($condition);
				return $this->$method($operator, $condition);
			} else {
				throw new Exception('Found unknown operator in query: ' . $operator);
			}
		} else { // hash format: 'column1'=>'value1', 'column2'=>'value2', ...
			return $this->buildHashCondition($condition);
		}
	}
Qiang Xue committed
501

Qiang Xue committed
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
	private function buildHashCondition($condition)
	{
		$parts = array();
		foreach ($condition as $column => $value) {
			if (is_array($value)) { // IN condition
				$parts[] = $this->buildInCondition('in', array($column, $value));
			} else {
				if (strpos($column, '(') === false) {
					$column = $this->quoteColumnName($column);
				}
				if ($value === null) {
					$parts[] = "$column IS NULL";
				} elseif (is_string($value)) {
					$parts[] = "$column=" . $this->connection->quoteValue($value);
				} else {
					$parts[] = "$column=$value";
Qiang Xue committed
518 519 520
				}
			}
		}
Qiang Xue committed
521
		return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
Qiang Xue committed
522
	}
Qiang Xue committed
523

Qiang Xue committed
524 525 526 527 528 529 530 531 532 533
	private function buildAndCondition($operator, $operands)
	{
		$parts = array();
		foreach ($operands as $operand) {
			if (is_array($operand)) {
				$operand = $this->buildCondition($operand);
			}
			if ($operand !== '') {
				$parts[] = $operand;
			}
Qiang Xue committed
534
		}
Qiang Xue committed
535 536 537 538 539 540 541 542 543 544 545 546 547 548
		if ($parts !== array()) {
			return '(' . implode(") $operator (", $parts) . ')';
		} else {
			return '';
		}
	}

	private function buildBetweenCondition($operator, $operands)
	{
		if (!isset($operands[0], $operands[1], $operands[2])) {
			throw new Exception("Operator '$operator' requires three operands.");
		}

		list($column, $value1, $value2) = $operands;
Qiang Xue committed
549 550

		if (strpos($column, '(') === false) {
Qiang Xue committed
551
			$column = $this->quoteColumnName($column);
Qiang Xue committed
552
		}
Qiang Xue committed
553 554
		$value1 = is_string($value1) ? $this->connection->quoteValue($value1) : (string)$value1;
		$value2 = is_string($value2) ? $this->connection->quoteValue($value2) : (string)$value2;
Qiang Xue committed
555

Qiang Xue committed
556 557 558 559 560 561 562
		return "$column $operator $value1 AND $value2";
	}

	private function buildInCondition($operator, $operands)
	{
		if (!isset($operands[0], $operands[1])) {
			throw new Exception("Operator '$operator' requires two operands.");
Qiang Xue committed
563 564
		}

Qiang Xue committed
565 566
		list($column, $values) = $operands;

Qiang Xue committed
567 568 569 570
		if (!is_array($values)) {
			$values = array($values);
		}

Qiang Xue committed
571 572
		if ($values === array()) {
			return $operator === 'in' ? '0=1' : '';
Qiang Xue committed
573 574
		}

Qiang Xue committed
575 576 577
		foreach ($values as $i => $value) {
			$values[$i] = is_string($value) ? $this->connection->quoteValue($value) : (string)$value;
		}
Qiang Xue committed
578

Qiang Xue committed
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
		if (strpos($column, '(') === false) {
			$column = $this->quoteColumnName($column);
		}

		return "$column $operator (" . implode(', ', $values) . ')';
	}

	private function buildLikeCondition($operator, $operands)
	{
		if (!isset($operands[0], $operands[1])) {
			throw new Exception("Operator '$operator' requires two operands.");
		}

		list($column, $values) = $operands;

		if (!is_array($values)) {
			$values = array($values);
		}

		if ($values === array()) {
			return $operator === 'like' || $operator === 'or like' ? '0=1' : '';
		}

		if ($operator === 'like' || $operator === 'not like') {
			$andor = ' and ';
		} else {
			$andor = ' or ';
			$operator = $operator === 'or like' ? 'like' : 'not like';
		}

		if (strpos($column, '(') === false) {
			$column = $this->quoteColumnName($column);
		}

		$parts = array();
		foreach ($values as $value) {
			$parts[] = "$column $operator " . $this->connection->quoteValue($value);
w  
Qiang Xue committed
616
		}
Qiang Xue committed
617

Qiang Xue committed
618
		return implode($andor, $parts);
w  
Qiang Xue committed
619 620
	}

Qiang Xue committed
621
	/**
Qiang Xue committed
622 623 624
	 * @param string|array $columns
	 * @param boolean $distinct
	 * @param string $selectOption
Qiang Xue committed
625 626
	 * @return string the SELECT clause built from [[query]].
	 */
Qiang Xue committed
627
	public function buildSelect($columns, $distinct = false, $selectOption = null)
w  
Qiang Xue committed
628
	{
Qiang Xue committed
629 630 631
		$select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
		if ($selectOption !== null) {
			$select .= ' ' . $selectOption;
w  
Qiang Xue committed
632
		}
w  
Qiang Xue committed
633

w  
Qiang Xue committed
634 635 636 637
		if (empty($columns)) {
			return $select . ' *';
		}

Qiang Xue committed
638
		if ($this->autoQuote) {
Qiang Xue committed
639
			$driver = $this->connection->driver;
Qiang Xue committed
640 641 642
			if (!is_array($columns)) {
				if (strpos($columns, '(') !== false) {
					return $select . ' ' . $columns;
Qiang Xue committed
643
				} else {
Qiang Xue committed
644 645 646 647 648 649 650
					$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) {
Qiang Xue committed
651
					if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
Qiang Xue committed
652
						$columns[$i] = $driver->quoteColumnName($matches[1]) . ' AS ' . $driver->quoteSimpleColumnName($matches[2]);
Qiang Xue committed
653
					} else {
Qiang Xue committed
654
						$columns[$i] = $driver->quoteColumnName($column);
Qiang Xue committed
655
					}
w  
Qiang Xue committed
656 657 658 659
				}
			}
		}

Qiang Xue committed
660 661 662 663 664
		if (is_array($columns)) {
			$columns = implode(', ', $columns);
		}

		return $select . ' ' . $columns;
w  
Qiang Xue committed
665 666
	}

Qiang Xue committed
667
	/**
Qiang Xue committed
668
	 * @param string|array $tables
Qiang Xue committed
669 670
	 * @return string the FROM clause built from [[query]].
	 */
Qiang Xue committed
671
	public function buildFrom($tables)
w  
Qiang Xue committed
672
	{
Qiang Xue committed
673
		if (empty($tables)) {
Qiang Xue committed
674 675 676
			return '';
		}

Qiang Xue committed
677
		if ($this->autoQuote) {
Qiang Xue committed
678
			$driver = $this->connection->driver;
Qiang Xue committed
679 680 681
			if (!is_array($tables)) {
				if (strpos($tables, '(') !== false) {
					return 'FROM ' . $tables;
Qiang Xue committed
682
				} else {
Qiang Xue committed
683
					$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
w  
Qiang Xue committed
684 685
				}
			}
Qiang Xue committed
686 687 688
			foreach ($tables as $i => $table) {
				if (strpos($table, '(') === false) {
					if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias
Qiang Xue committed
689
						$tables[$i] = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]);
Qiang Xue committed
690
					} else {
Qiang Xue committed
691
						$tables[$i] = $driver->quoteTableName($table);
Qiang Xue committed
692 693 694 695 696 697 698
					}
				}
			}
		}

		if (is_array($tables)) {
			$tables = implode(', ', $tables);
w  
Qiang Xue committed
699 700
		}

Qiang Xue committed
701
		return 'FROM ' . $tables;
w  
Qiang Xue committed
702
	}
w  
Qiang Xue committed
703

Qiang Xue committed
704
	/**
Qiang Xue committed
705
	 * @param string|array $joins
Qiang Xue committed
706 707
	 * @return string the JOIN clause built from [[query]].
	 */
Qiang Xue committed
708
	public function buildJoin($joins)
w  
Qiang Xue committed
709 710 711 712 713 714 715
	{
		if (empty($joins)) {
			return '';
		}
		if (is_string($joins)) {
			return $joins;
		}
w  
Qiang Xue committed
716

w  
Qiang Xue committed
717
		foreach ($joins as $i => $join) {
Qiang Xue committed
718
			if (is_array($join)) { // 0:join type, 1:table name, 2:on-condition
w  
Qiang Xue committed
719 720
				if (isset($join[0], $join[1])) {
					$table = $join[1];
Qiang Xue committed
721
					if ($this->autoQuote && strpos($table, '(') === false) {
Qiang Xue committed
722
						$driver = $this->connection->driver;
Qiang Xue committed
723
						if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
Qiang Xue committed
724
							$table = $driver->quoteTableName($matches[1]) . ' ' . $driver->quoteTableName($matches[2]);
Qiang Xue committed
725
						} else {
Qiang Xue committed
726
							$table = $driver->quoteTableName($table);
w  
Qiang Xue committed
727 728 729
						}
					}
					$joins[$i] = strtoupper($join[0]) . ' ' . $table;
Qiang Xue committed
730 731 732 733 734
					if (isset($join[2])) {
						$condition = $this->buildCondition($join[2]);
						if ($condition !== '') {
							$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
						}
w  
Qiang Xue committed
735
					}
Qiang Xue committed
736
				} else {
Qiang Xue committed
737
					throw new Exception('A join clause must be specified as an array of at least two elements.');
w  
Qiang Xue committed
738 739 740
				}
			}
		}
w  
Qiang Xue committed
741

Qiang Xue committed
742
		return implode($this->separator, $joins);
w  
Qiang Xue committed
743 744
	}

Qiang Xue committed
745
	/**
Qiang Xue committed
746
	 * @param string|array $condition
Qiang Xue committed
747 748
	 * @return string the WHERE clause built from [[query]].
	 */
Qiang Xue committed
749
	public function buildWhere($condition)
w  
Qiang Xue committed
750
	{
Qiang Xue committed
751 752
		$where = $this->buildCondition($condition);
		return $where === '' ? '' : 'WHERE ' . $where;
w  
Qiang Xue committed
753 754
	}

Qiang Xue committed
755
	/**
Qiang Xue committed
756 757
	 * @param string|array $columns
	 * @return string the GROUP BY clause
Qiang Xue committed
758
	 */
Qiang Xue committed
759
	public function buildGroupBy($columns)
w  
Qiang Xue committed
760
	{
Qiang Xue committed
761
		if (empty($columns)) {
w  
Qiang Xue committed
762
			return '';
Qiang Xue committed
763
		} else {
Qiang Xue committed
764
			return 'GROUP BY ' . $this->buildColumns($columns);
w  
Qiang Xue committed
765
		}
w  
Qiang Xue committed
766 767
	}

Qiang Xue committed
768
	/**
Qiang Xue committed
769
	 * @param string|array $condition
Qiang Xue committed
770 771
	 * @return string the HAVING clause built from [[query]].
	 */
Qiang Xue committed
772
	public function buildHaving($condition)
w  
Qiang Xue committed
773
	{
Qiang Xue committed
774 775
		$having = $this->buildCondition($condition);
		return $having === '' ? '' : 'HAVING ' . $having;
w  
Qiang Xue committed
776 777
	}

Qiang Xue committed
778
	/**
Qiang Xue committed
779
	 * @param string|array $columns
Qiang Xue committed
780 781
	 * @return string the ORDER BY clause built from [[query]].
	 */
Qiang Xue committed
782
	public function buildOrderBy($columns)
w  
Qiang Xue committed
783
	{
Qiang Xue committed
784
		if (empty($columns)) {
w  
Qiang Xue committed
785 786
			return '';
		}
Qiang Xue committed
787
		if ($this->autoQuote) {
Qiang Xue committed
788
			$driver = $this->connection->driver;
Qiang Xue committed
789 790 791
			if (!is_array($columns)) {
				if (strpos($columns, '(') !== false) {
					return 'ORDER BY ' . $columns;
Qiang Xue committed
792
				} else {
Qiang Xue committed
793
					$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
w  
Qiang Xue committed
794 795
				}
			}
Qiang Xue committed
796 797 798 799 800
			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)) {
Qiang Xue committed
801
						$columns[$i] = $driver->quoteColumnName($matches[1]) . ' ' . $matches[2];
Qiang Xue committed
802
					} else {
Qiang Xue committed
803
						$columns[$i] = $driver->quoteColumnName($column);
Qiang Xue committed
804 805 806 807 808 809
					}
				}
			}
		}
		if (is_array($columns)) {
			$columns = implode(', ', $columns);
w  
Qiang Xue committed
810
		}
Qiang Xue committed
811
		return 'ORDER BY ' . $columns;
w  
Qiang Xue committed
812 813
	}

Qiang Xue committed
814
	/**
Qiang Xue committed
815 816
	 * @param integer $limit
	 * @param integer $offset
Qiang Xue committed
817 818
	 * @return string the LIMIT and OFFSET clauses built from [[query]].
	 */
Qiang Xue committed
819
	public function buildLimit($limit, $offset)
w  
Qiang Xue committed
820
	{
w  
Qiang Xue committed
821
		$sql = '';
Qiang Xue committed
822 823
		if ($limit !== null && $limit >= 0) {
			$sql = 'LIMIT ' . (int)$limit;
w  
Qiang Xue committed
824
		}
Qiang Xue committed
825 826
		if ($offset > 0) {
			$sql .= ' OFFSET ' . (int)$offset;
w  
Qiang Xue committed
827 828
		}
		return ltrim($sql);
w  
Qiang Xue committed
829 830
	}

Qiang Xue committed
831
	/**
Qiang Xue committed
832
	 * @param string|array $unions
Qiang Xue committed
833 834
	 * @return string the UNION clause built from [[query]].
	 */
Qiang Xue committed
835
	public function buildUnion($unions)
w  
Qiang Xue committed
836
	{
w  
Qiang Xue committed
837 838 839 840 841 842 843
		if (empty($unions)) {
			return '';
		}
		if (!is_array($unions)) {
			$unions = array($unions);
		}
		foreach ($unions as $i => $union) {
Qiang Xue committed
844 845
			if ($union instanceof BaseQuery) {
				$unions[$i] = $this->build($union);
w  
Qiang Xue committed
846 847 848
			}
		}
		return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)";
w  
Qiang Xue committed
849
	}
Qiang Xue committed
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871

	/**
	 * Processes columns and properly quote them if necessary.
	 * This method will quote columns if [[autoQuote]] is true.
	 * It will join all columns into a string with comma as separators.
	 * @param string|array $columns the columns to be processed
	 * @return string the processing result
	 */
	protected function buildColumns($columns)
	{
		if ($this->autoQuote) {
			if (!is_array($columns)) {
				if (strpos($columns, '(') !== false) {
					return $columns;
				} else {
					$columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
				}
			}
			foreach ($columns as $i => $column) {
				if (is_object($column)) {
					$columns[$i] = (string)$column;
				} elseif (strpos($column, '(') === false) {
Qiang Xue committed
872
					$columns[$i] = $this->quoteColumnName($column);
Qiang Xue committed
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888
				}
			}
		}
		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) {
Qiang Xue committed
889
			return $this->connection->quoteTableName($name, $simple);
Qiang Xue committed
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
		} 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) {
Qiang Xue committed
905
			return $this->connection->quoteColumnName($name, $simple);
Qiang Xue committed
906 907 908 909
		} else {
			return $name;
		}
	}
w  
Qiang Xue committed
910
}