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
----------------------------
- 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)
2.0.0 alpha, December 1, 2013
......
......@@ -509,13 +509,16 @@ class QueryBuilder extends Object
public function buildLimit($limit, $offset)
{
$sql = '';
if ($limit !== null && $limit >= 0) {
$sql = 'LIMIT ' . (int)$limit;
if (is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0') {
$sql = 'LIMIT ' . $offset;
}
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
if (is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0) {
$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
- Bug #1998: Unchecked required checkbox never pass client validation (klevron)
- 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 #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: 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)
......@@ -112,6 +113,7 @@ Yii Framework 2 Change Log
- Enh: Added support for using timeZone with `yii\base\Formatter` (dizews)
- Enh: Added `yii\web\View::POS_LOAD` (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 #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)
......
......@@ -720,16 +720,36 @@ class QueryBuilder extends \yii\base\Object
public function buildLimit($limit, $offset)
{
$sql = '';
if ($limit !== null && $limit >= 0) {
$sql = 'LIMIT ' . (int)$limit;
if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . $limit;
}
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . $offset;
}
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 $params the binding parameters to be populated
* @return string the UNION clause built from [[Query::$union]].
......
......@@ -77,13 +77,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
// limit is not optional in CUBRID
// 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."
if ($limit !== null && $limit >= 0) {
$sql = 'LIMIT ' . (int)$limit;
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . $limit;
if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . $offset;
}
} elseif ($offset > 0) {
$sql = 'LIMIT 9223372036854775807 OFFSET ' . (int)$offset; // 2^63-1
} elseif ($this->hasOffset($offset)) {
$sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
}
return $sql;
}
......
......@@ -56,14 +56,18 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/
public function buildLimit($limit, $offset = 0)
{
$sql = '';
if ($offset !== null && $offset >= 0) {
$sql = 'OFFSET ' . (int)$offset . ' ROWS';
if ($limit !== null && $limit >= 0) {
$sql .= ' FETCH NEXT ' . (int)$limit . ' ROWS ONLY';
}
$hasOffset = $this->hasOffset($offset);
$hasLimit = $this->hasLimit($limit);
if ($hasOffset || $hasLimit) {
// http://technet.microsoft.com/en-us/library/gg699618.aspx
$sql = 'OFFSET ' . ($hasOffset ? $offset : '0');
if ($hasLimit) {
$sql .= " FETCH NEXT $limit ROWS ONLY";
}
return $sql;
} else {
return '';
}
}
// public function resetSequence($table, $value = null)
......
......@@ -147,17 +147,18 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset)
{
$sql = '';
if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . $limit;
if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . $offset;
}
} elseif ($this->hasOffset($offset)) {
// limit is not optional in MySQL
// http://stackoverflow.com/a/271650/1106908
// http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240
if ($limit !== null && $limit >= 0) {
$sql = 'LIMIT ' . (int)$limit;
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
}
} elseif ($offset > 0) {
$sql = 'LIMIT ' . (int)$offset . ', 18446744073709551615'; // 2^64-1
$sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1
}
return $sql;
}
}
......@@ -32,9 +32,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
];
$this->sql = implode($this->separator, array_filter($clauses));
if ($query->limit !== null || $query->offset !== null) {
$this->sql = $this->buildLimit($query->limit, $query->offset);
}
$unions = $this->buildUnion($query->union, $params);
if ($unions !== '') {
......@@ -46,33 +44,27 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset)
{
if (($limit < 0) && ($offset < 0)) {
return $this->sql;
}
$filters = [];
if ($offset > 0) {
$filters[] = 'rowNumId > ' . (int)$offset;
if ($this->hasOffset($offset) > 0) {
$filters[] = 'rowNumId > ' . $offset;
}
if ($limit >= 0) {
$filters[] = 'rownum <= ' . (int)$limit;
if ($this->hasLimit($limit)) {
$filters[] = 'rownum <= ' . $limit;
}
if (count($filters) > 0) {
if (!empty($filters)) {
$filter = implode(' and ', $filters);
$filter = " WHERE " . $filter;
} else {
$filter = '';
}
$sql = <<<EOD
return <<<EOD
WITH USER_SQL AS ({$this->sql}),
PAGINATION AS (SELECT USER_SQL.*, rownum as rowNumId FROM USER_SQL)
SELECT *
FROM PAGINATION
{$filter}
WHERE $filter
EOD;
return $sql;
} else {
return $this->sql;
}
}
......
......@@ -260,15 +260,15 @@ class QueryBuilder extends \yii\db\QueryBuilder
public function buildLimit($limit, $offset)
{
$sql = '';
if ($this->hasLimit($limit)) {
$sql = 'LIMIT ' . $limit;
if ($this->hasOffset($offset)) {
$sql .= ' OFFSET ' . $offset;
}
} elseif ($this->hasOffset($offset)) {
// limit is not optional in SQLite
// http://www.sqlite.org/syntaxdiagrams.html#select-stmt
if ($limit !== null && $limit >= 0) {
$sql = 'LIMIT ' . (int)$limit;
if ($offset > 0) {
$sql .= ' OFFSET ' . (int)$offset;
}
} elseif ($offset > 0) {
$sql = 'LIMIT 9223372036854775807 OFFSET ' . (int)$offset; // 2^63-1
$sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
}
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