Commit 06feccff by Qiang Xue

refactored query builder.

finished Sort.
parent 1f522d22
......@@ -35,9 +35,19 @@ namespace yii\db;
class Query extends \yii\base\Component
{
/**
* @var string|array the columns being selected. This refers to the SELECT clause in a SQL
* statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
* If not set, if means all columns.
* Sort ascending
* @see orderBy
*/
const SORT_ASC = false;
/**
* Sort ascending
* @see orderBy
*/
const SORT_DESC = true;
/**
* @var array the columns being selected. For example, `array('id', 'name')`.
* This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns.
* @see select()
*/
public $select;
......@@ -52,8 +62,8 @@ class Query extends \yii\base\Component
*/
public $distinct;
/**
* @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
* It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
* @var array the table(s) to be selected from. For example, `array('tbl_user', 'tbl_post')`.
* This is used to construct the FROM clause in a SQL statement.
* @see from()
*/
public $from;
......@@ -73,20 +83,33 @@ class Query extends \yii\base\Component
*/
public $offset;
/**
* @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement.
* It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`).
* @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
* can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
* If that is the case, the expressions will be converted into strings without any change.
*/
public $orderBy;
/**
* @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement.
* It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
* @var array how to group the query results. For example, `array('company', 'department')`.
* This is used to construct the GROUP BY clause in a SQL statement.
*/
public $groupBy;
/**
* @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement.
* It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
* `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
* @see join()
* @var array how to join with other tables. Each array element represents the specification
* of one join which has the following structure:
*
* ~~~
* array($joinType, $tableName, $joinCondition)
* ~~~
*
* For example,
*
* ~~~
* array(
* array('INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'),
* array('LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'),
* )
* ~~~
*/
public $join;
/**
......@@ -95,9 +118,8 @@ class Query extends \yii\base\Component
*/
public $having;
/**
* @var string|Query[] the UNION clause(s) in a SQL statement. This can be either a string
* representing a single UNION clause or an array representing multiple UNION clauses.
* Each union clause can be a string or a `Query` object which refers to the SQL statement.
* @var array this is used to construct the UNION clause(s) in a SQL statement.
* Each array element can be either a string or a [[Query]] object representing a sub-query.
*/
public $union;
/**
......@@ -134,6 +156,9 @@ class Query extends \yii\base\Component
*/
public function select($columns, $option = null)
{
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->select = $columns;
$this->selectOption = $option;
return $this;
......@@ -161,6 +186,9 @@ class Query extends \yii\base\Component
*/
public function from($tables)
{
if (!is_array($tables)) {
$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
}
$this->from = $tables;
return $this;
}
......@@ -360,10 +388,13 @@ class Query extends \yii\base\Component
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addGroup()
* @see addGroupBy()
*/
public function groupBy($columns)
{
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->groupBy = $columns;
return $this;
}
......@@ -375,19 +406,16 @@ class Query extends \yii\base\Component
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see group()
* @see groupBy()
*/
public function addGroup($columns)
public function addGroupBy($columns)
{
if (empty($this->groupBy)) {
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
if ($this->groupBy === null) {
$this->groupBy = $columns;
} else {
if (!is_array($this->groupBy)) {
$this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY);
}
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->groupBy = array_merge($this->groupBy, $columns);
}
return $this;
......@@ -454,43 +482,58 @@ class Query extends \yii\base\Component
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addOrder()
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $columns;
$this->orderBy = $this->normalizeOrderBy($columns);
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see order()
* @see orderBy()
*/
public function addOrderBy($columns)
{
if (empty($this->orderBy)) {
$columns = $this->normalizeOrderBy($columns);
if ($this->orderBy === null) {
$this->orderBy = $columns;
} else {
if (!is_array($this->orderBy)) {
$this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY);
}
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
protected function normalizeOrderBy($columns)
{
if (is_array($columns)) {
return $columns;
} else {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
$result = array();
foreach ($columns as $column) {
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
} else {
$result[$column] = self::SORT_ASC;
}
}
return $result;
}
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
......
......@@ -60,10 +60,10 @@ class QueryBuilder extends \yii\base\Object
$this->buildFrom($query->from),
$this->buildJoin($query->join),
$this->buildWhere($query->where),
$this->buildGroup($query->groupBy),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having),
$this->buildUnion($query->union),
$this->buildOrder($query->orderBy),
$this->buildOrderBy($query->orderBy),
$this->buildLimit($query->limit, $query->offset),
);
return implode($this->separator, array_filter($clauses));
......@@ -673,7 +673,7 @@ class QueryBuilder extends \yii\base\Object
}
/**
* @param string|array $columns
* @param array $columns
* @param boolean $distinct
* @param string $selectOption
* @return string the SELECT clause built from [[query]].
......@@ -689,13 +689,6 @@ class QueryBuilder extends \yii\base\Object
return $select . ' *';
}
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;
......@@ -716,7 +709,7 @@ class QueryBuilder extends \yii\base\Object
}
/**
* @param string|array $tables
* @param array $tables
* @return string the FROM clause built from [[query]].
*/
public function buildFrom($tables)
......@@ -725,13 +718,6 @@ class QueryBuilder extends \yii\base\Object
return '';
}
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
......@@ -752,37 +738,36 @@ class QueryBuilder extends \yii\base\Object
/**
* @param string|array $joins
* @return string the JOIN clause built from [[query]].
* @throws Exception if the $joins parameter is not in proper format
*/
public function buildJoin($joins)
{
if (empty($joins)) {
return '';
}
if (is_string($joins)) {
return $joins;
}
foreach ($joins as $i => $join) {
if (is_array($join)) { // 0:join type, 1:table name, 2:on-condition
if (isset($join[0], $join[1])) {
$table = $join[1];
if (strpos($table, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
$table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
} else {
$table = $this->db->quoteTableName($table);
}
if (is_object($join)) {
$joins[$i] = (string)$join;
} elseif (is_array($join) && isset($join[0], $join[1])) {
// 0:join type, 1:table name, 2:on-condition
$table = $join[1];
if (strpos($table, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
$table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
} else {
$table = $this->db->quoteTableName($table);
}
$joins[$i] = $join[0] . ' ' . $table;
if (isset($join[2])) {
$condition = $this->buildCondition($join[2]);
if ($condition !== '') {
$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
}
}
$joins[$i] = $join[0] . ' ' . $table;
if (isset($join[2])) {
$condition = $this->buildCondition($join[2]);
if ($condition !== '') {
$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
}
} else {
throw new Exception('A join clause must be specified as an array of at least two elements.');
}
} else {
throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
}
}
......@@ -800,16 +785,12 @@ class QueryBuilder extends \yii\base\Object
}
/**
* @param string|array $columns
* @param array $columns
* @return string the GROUP BY clause
*/
public function buildGroup($columns)
public function buildGroupBy($columns)
{
if (empty($columns)) {
return '';
} else {
return 'GROUP BY ' . $this->buildColumns($columns);
}
return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
}
/**
......@@ -823,36 +804,24 @@ class QueryBuilder extends \yii\base\Object
}
/**
* @param string|array $columns
* @param array $columns
* @return string the ORDER BY clause built from [[query]].
*/
public function buildOrder($columns)
public function buildOrderBy($columns)
{
if (empty($columns)) {
return '';
}
if (!is_array($columns)) {
if (strpos($columns, '(') !== false) {
return 'ORDER BY ' . $columns;
$orders = array();
foreach ($columns as $name => $direction) {
if (is_object($direction)) {
$orders[] = (string)$direction;
} 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] = $this->db->quoteColumnName($matches[1]) . ' ' . $matches[2];
} else {
$columns[$i] = $this->db->quoteColumnName($column);
}
$orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : '');
}
}
if (is_array($columns)) {
$columns = implode(', ', $columns);
}
return 'ORDER BY ' . $columns;
return 'ORDER BY ' . implode(', ', $orders);
}
/**
......@@ -873,7 +842,7 @@ class QueryBuilder extends \yii\base\Object
}
/**
* @param string|array $unions
* @param array $unions
* @return string the UNION clause built from [[query]].
*/
public function buildUnion($unions)
......@@ -881,9 +850,6 @@ class QueryBuilder extends \yii\base\Object
if (empty($unions)) {
return '';
}
if (!is_array($unions)) {
$unions = array($unions);
}
foreach ($unions as $i => $union) {
if ($union instanceof Query) {
$unions[$i] = $this->build($union);
......
......@@ -65,7 +65,8 @@ use Yii;
class Pagination extends \yii\base\Object
{
/**
* @var string name of the GET variable storing the current page index. Defaults to 'page'.
* @var string name of the parameter storing the current page index. Defaults to 'page'.
* @see params
*/
public $pageVar = 'page';
/**
......
......@@ -14,13 +14,13 @@ class QueryTest extends \yiiunit\MysqlTestCase
// default
$query = new Query;
$query->select('*');
$this->assertEquals('*', $query->select);
$this->assertEquals(array('*'), $query->select);
$this->assertNull($query->distinct);
$this->assertEquals(null, $query->selectOption);
$query = new Query;
$query->select('id, name', 'something')->distinct(true);
$this->assertEquals('id, name', $query->select);
$this->assertEquals(array('id','name'), $query->select);
$this->assertTrue($query->distinct);
$this->assertEquals('something', $query->selectOption);
}
......@@ -29,7 +29,7 @@ class QueryTest extends \yiiunit\MysqlTestCase
{
$query = new Query;
$query->from('tbl_user');
$this->assertEquals('tbl_user', $query->from);
$this->assertEquals(array('tbl_user'), $query->from);
}
function testWhere()
......@@ -57,12 +57,12 @@ class QueryTest extends \yiiunit\MysqlTestCase
{
$query = new Query;
$query->groupBy('team');
$this->assertEquals('team', $query->groupBy);
$this->assertEquals(array('team'), $query->groupBy);
$query->addGroup('company');
$query->addGroupBy('company');
$this->assertEquals(array('team', 'company'), $query->groupBy);
$query->addGroup('age');
$query->addGroupBy('age');
$this->assertEquals(array('team', 'company', 'age'), $query->groupBy);
}
......@@ -86,13 +86,19 @@ class QueryTest extends \yiiunit\MysqlTestCase
{
$query = new Query;
$query->orderBy('team');
$this->assertEquals('team', $query->orderBy);
$this->assertEquals(array('team' => false), $query->orderBy);
$query->addOrderBy('company');
$this->assertEquals(array('team', 'company'), $query->orderBy);
$this->assertEquals(array('team' => false, 'company' => false), $query->orderBy);
$query->addOrderBy('age');
$this->assertEquals(array('team', 'company', 'age'), $query->orderBy);
$this->assertEquals(array('team' => false, 'company' => false, 'age' => false), $query->orderBy);
$query->addOrderBy(array('age' => true));
$this->assertEquals(array('team' => false, 'company' => false, 'age' => true), $query->orderBy);
$query->addOrderBy('age ASC, company DESC');
$this->assertEquals(array('team' => false, 'company' => true, 'age' => false), $query->orderBy);
}
function testLimitOffset()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment