Commit 8e11629a by Qiang Xue

Fixes #2160: SphinxQL does not support OFFSET

Improved `QueryBuilder::buildLimit()` to support big numbers
parent d53f4dd9
...@@ -5,6 +5,7 @@ Yii Framework 2 sphinx extension Change Log ...@@ -5,6 +5,7 @@ Yii Framework 2 sphinx extension Change Log
---------------------------- ----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) - Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul) - Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
2.0.0 alpha, December 1, 2013 2.0.0 alpha, December 1, 2013
......
...@@ -509,13 +509,16 @@ class QueryBuilder extends Object ...@@ -509,13 +509,16 @@ class QueryBuilder extends Object
public function buildLimit($limit, $offset) public function buildLimit($limit, $offset)
{ {
$sql = ''; $sql = '';
if ($limit !== null && $limit >= 0) { if (is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0') {
$sql = 'LIMIT ' . (int)$limit; $sql = 'LIMIT ' . $offset;
} }
if ($offset > 0) { if (is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0) {
$sql .= ' OFFSET ' . (int)$offset; $sql = $sql === '' ? "LIMIT $limit" : "$sql,$limit";
} elseif ($sql !== '') {
$sql .= ',1000'; // this is the default limit by sphinx
} }
return ltrim($sql);
return $sql;
} }
/** /**
......
...@@ -38,6 +38,7 @@ Yii Framework 2 Change Log ...@@ -38,6 +38,7 @@ Yii Framework 2 Change Log
- Bug #1998: Unchecked required checkbox never pass client validation (klevron) - Bug #1998: Unchecked required checkbox never pass client validation (klevron)
- Bug #2084: AssetController adjusting CSS URLs declared at same line fixed (klimov-paul) - Bug #2084: AssetController adjusting CSS URLs declared at same line fixed (klimov-paul)
- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue) - Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue) - Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue)
- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
...@@ -112,6 +113,7 @@ Yii Framework 2 Change Log ...@@ -112,6 +113,7 @@ Yii Framework 2 Change Log
- Enh: Added support for using timeZone with `yii\base\Formatter` (dizews) - Enh: Added support for using timeZone with `yii\base\Formatter` (dizews)
- Enh: Added `yii\web\View::POS_LOAD` (qiangxue) - Enh: Added `yii\web\View::POS_LOAD` (qiangxue)
- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue) - Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue)
- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue) - Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue) - Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue) - Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
......
...@@ -720,16 +720,36 @@ class QueryBuilder extends \yii\base\Object ...@@ -720,16 +720,36 @@ class QueryBuilder extends \yii\base\Object
public function buildLimit($limit, $offset) public function buildLimit($limit, $offset)
{ {
$sql = ''; $sql = '';
if ($limit !== null && $limit >= 0) { if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . (int)$limit; $sql = 'LIMIT ' . $limit;
} }
if ($offset > 0) { if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . (int)$offset; $sql .= ' OFFSET ' . $offset;
} }
return ltrim($sql); return ltrim($sql);
} }
/** /**
* Checks to see if the given limit is effective.
* @param mixed $limit the given limit
* @return boolean whether the limit is effective
*/
protected function hasLimit($limit)
{
return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
}
/**
* Checks to see if the given offset is effective.
* @param mixed $offset the given offset
* @return boolean whether the offset is effective
*/
protected function hasOffset($offset)
{
return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
}
/**
* @param array $unions * @param array $unions
* @param array $params the binding parameters to be populated * @param array $params the binding parameters to be populated
* @return string the UNION clause built from [[Query::$union]]. * @return string the UNION clause built from [[Query::$union]].
......
...@@ -77,13 +77,13 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -77,13 +77,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
// limit is not optional in CUBRID // limit is not optional in CUBRID
// http://www.cubrid.org/manual/90/en/LIMIT%20Clause // http://www.cubrid.org/manual/90/en/LIMIT%20Clause
// "You can specify a very big integer for row_count to display to the last row, starting from a specific row." // "You can specify a very big integer for row_count to display to the last row, starting from a specific row."
if ($limit !== null && $limit >= 0) { if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . (int)$limit; $sql = 'LIMIT ' . $limit;
if ($offset > 0) { if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . (int)$offset; $sql .= ' OFFSET ' . $offset;
} }
} elseif ($offset > 0) { } elseif ($this->hasOffset($offset)) {
$sql = 'LIMIT 9223372036854775807 OFFSET ' . (int)$offset; // 2^63-1 $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
} }
return $sql; return $sql;
} }
......
...@@ -56,14 +56,18 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -56,14 +56,18 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/ */
public function buildLimit($limit, $offset = 0) public function buildLimit($limit, $offset = 0)
{ {
$sql = ''; $hasOffset = $this->hasOffset($offset);
if ($offset !== null && $offset >= 0) { $hasLimit = $this->hasLimit($limit);
$sql = 'OFFSET ' . (int)$offset . ' ROWS'; if ($hasOffset || $hasLimit) {
if ($limit !== null && $limit >= 0) { // http://technet.microsoft.com/en-us/library/gg699618.aspx
$sql .= ' FETCH NEXT ' . (int)$limit . ' ROWS ONLY'; $sql = 'OFFSET ' . ($hasOffset ? $offset : '0');
if ($hasLimit) {
$sql .= " FETCH NEXT $limit ROWS ONLY";
} }
return $sql;
} else {
return '';
} }
return $sql;
} }
// public function resetSequence($table, $value = null) // public function resetSequence($table, $value = null)
...@@ -132,4 +136,4 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -132,4 +136,4 @@ class QueryBuilder extends \yii\db\QueryBuilder
$enable = $check ? 'CHECK' : 'NOCHECK'; $enable = $check ? 'CHECK' : 'NOCHECK';
return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL"; return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL";
} }
} }
\ No newline at end of file
...@@ -147,17 +147,18 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -147,17 +147,18 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset) public function buildLimit($limit, $offset)
{ {
$sql = ''; $sql = '';
// limit is not optional in MySQL if ($this->hasLimit($limit)) {
// http://stackoverflow.com/a/271650/1106908 $sql = 'LIMIT ' . $limit;
// http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240 if ($this->hasOffset($offset)) {
if ($limit !== null && $limit >= 0) { $sql .= ' OFFSET ' . $offset;
$sql = 'LIMIT ' . (int)$limit;
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
} }
} elseif ($offset > 0) { } elseif ($this->hasOffset($offset)) {
$sql = 'LIMIT ' . (int)$offset . ', 18446744073709551615'; // 2^64-1 // limit is not optional in MySQL
// http://stackoverflow.com/a/271650/1106908
// http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240
$sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1
} }
return $sql; return $sql;
} }
} }
...@@ -32,9 +32,7 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -32,9 +32,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
]; ];
$this->sql = implode($this->separator, array_filter($clauses)); $this->sql = implode($this->separator, array_filter($clauses));
if ($query->limit !== null || $query->offset !== null) { $this->sql = $this->buildLimit($query->limit, $query->offset);
$this->sql = $this->buildLimit($query->limit, $query->offset);
}
$unions = $this->buildUnion($query->union, $params); $unions = $this->buildUnion($query->union, $params);
if ($unions !== '') { if ($unions !== '') {
...@@ -46,33 +44,27 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -46,33 +44,27 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset) public function buildLimit($limit, $offset)
{ {
if (($limit < 0) && ($offset < 0)) {
return $this->sql;
}
$filters = []; $filters = [];
if ($offset > 0) { if ($this->hasOffset($offset) > 0) {
$filters[] = 'rowNumId > ' . (int)$offset; $filters[] = 'rowNumId > ' . $offset;
} }
if ($limit >= 0) { if ($this->hasLimit($limit)) {
$filters[] = 'rownum <= ' . (int)$limit; $filters[] = 'rownum <= ' . $limit;
} }
if (count($filters) > 0) { if (!empty($filters)) {
$filter = implode(' and ', $filters); $filter = implode(' and ', $filters);
$filter = " WHERE " . $filter; return <<<EOD
} else {
$filter = '';
}
$sql = <<<EOD
WITH USER_SQL AS ({$this->sql}), WITH USER_SQL AS ({$this->sql}),
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL) PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL)
SELECT * SELECT *
FROM PAGINATION FROM PAGINATION
{$filter} WHERE $filter
EOD; EOD;
return $sql; } else {
return $this->sql;
}
} }
......
...@@ -260,15 +260,15 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -260,15 +260,15 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset) public function buildLimit($limit, $offset)
{ {
$sql = ''; $sql = '';
// limit is not optional in SQLite if ($this->hasLimit($limit)) {
// http://www.sqlite.org/syntaxdiagrams.html#select-stmt $sql = 'LIMIT ' . $limit;
if ($limit !== null && $limit >= 0) { if ($this->hasOffset($offset)) {
$sql = 'LIMIT ' . (int)$limit; $sql .= ' OFFSET ' . $offset;
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
} }
} elseif ($offset > 0) { } elseif ($this->hasOffset($offset)) {
$sql = 'LIMIT 9223372036854775807 OFFSET ' . (int)$offset; // 2^63-1 // limit is not optional in SQLite
// http://www.sqlite.org/syntaxdiagrams.html#select-stmt
$sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
} }
return $sql; return $sql;
} }
......
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