Skip to content

Commit 03c147e

Browse files
author
Jared Dunham
committed
Updated offset
1 parent ca7970e commit 03c147e

File tree

2 files changed

+34
-99
lines changed

2 files changed

+34
-99
lines changed

src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php

Lines changed: 31 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ class SqlServerGrammar extends Grammar
1919
'&', '&=', '|', '|=', '^', '^=',
2020
];
2121

22+
/**
23+
* The components that make up a select clause.
24+
*
25+
* @var string[]
26+
*/
27+
protected $selectComponents = [
28+
'aggregate',
29+
'columns',
30+
'from',
31+
'joins',
32+
'wheres',
33+
'groups',
34+
'havings',
35+
'orders',
36+
'offset',
37+
'limit',
38+
'lock',
39+
];
40+
2241
/**
2342
* Compile a select query into SQL.
2443
*
@@ -27,26 +46,12 @@ class SqlServerGrammar extends Grammar
2746
*/
2847
public function compileSelect(Builder $query)
2948
{
30-
if (! $query->offset) {
31-
return parent::compileSelect($query);
32-
}
33-
34-
if (is_null($query->columns)) {
35-
$query->columns = ['*'];
49+
// An ORDER BY clause is required for offset to work
50+
if($query->offset && empty($query->orders)){
51+
$query->orders[] = ['sql' => '(SELECT 0)'];
3652
}
3753

38-
$components = $this->compileComponents($query);
39-
40-
if (! empty($components['orders'])) {
41-
return parent::compileSelect($query)." offset {$query->offset} rows fetch next {$query->limit} rows only";
42-
}
43-
44-
// If an offset is present on the query, we will need to wrap the query in
45-
// a big "ANSI" offset syntax block. This is very nasty compared to the
46-
// other database systems but is necessary for implementing features.
47-
return $this->compileAnsiOffset(
48-
$query, $components
49-
);
54+
return parent::compileSelect($query);
5055
}
5156

5257
/**
@@ -235,69 +240,6 @@ protected function compileHavingBitwise($having)
235240
return '('.$column.' '.$having['operator'].' '.$parameter.') != 0';
236241
}
237242

238-
/**
239-
* Create a full ANSI offset clause for the query.
240-
*
241-
* @param \Illuminate\Database\Query\Builder $query
242-
* @param array $components
243-
* @return string
244-
*/
245-
protected function compileAnsiOffset(Builder $query, $components)
246-
{
247-
// An ORDER BY clause is required to make this offset query work, so if one does
248-
// not exist we'll just create a dummy clause to trick the database and so it
249-
// does not complain about the queries for not having an "order by" clause.
250-
if (empty($components['orders'])) {
251-
$components['orders'] = 'order by (select 0)';
252-
}
253-
254-
// We need to add the row number to the query so we can compare it to the offset
255-
// and limit values given for the statements. So we will add an expression to
256-
// the "select" that will give back the row numbers on each of the records.
257-
$components['columns'] .= $this->compileOver($components['orders']);
258-
259-
unset($components['orders']);
260-
261-
if ($this->queryOrderContainsSubquery($query)) {
262-
$query->bindings = $this->sortBindingsForSubqueryOrderBy($query);
263-
}
264-
265-
// Next we need to calculate the constraints that should be placed on the query
266-
// to get the right offset and limit from our query but if there is no limit
267-
// set we will just handle the offset only since that is all that matters.
268-
$sql = $this->concatenate($components);
269-
270-
return $this->compileTableExpression($sql, $query);
271-
}
272-
273-
/**
274-
* Compile the over statement for a table expression.
275-
*
276-
* @param string $orderings
277-
* @return string
278-
*/
279-
protected function compileOver($orderings)
280-
{
281-
return ", row_number() over ({$orderings}) as row_num";
282-
}
283-
284-
/**
285-
* Determine if the query's order by clauses contain a subquery.
286-
*
287-
* @param \Illuminate\Database\Query\Builder $query
288-
* @return bool
289-
*/
290-
protected function queryOrderContainsSubquery($query)
291-
{
292-
if (! is_array($query->orders)) {
293-
return false;
294-
}
295-
296-
return Arr::first($query->orders, function ($value) {
297-
return $this->isExpression($value['column'] ?? null);
298-
}, false) !== false;
299-
}
300-
301243
/**
302244
* Move the order bindings to be after the "select" statement to account for an order by subquery.
303245
*
@@ -311,20 +253,6 @@ protected function sortBindingsForSubqueryOrderBy($query)
311253
});
312254
}
313255

314-
/**
315-
* Compile a common table expression for a query.
316-
*
317-
* @param string $sql
318-
* @param \Illuminate\Database\Query\Builder $query
319-
* @return string
320-
*/
321-
protected function compileTableExpression($sql, $query)
322-
{
323-
$constraint = $this->compileRowConstraint($query);
324-
325-
return "select * from ({$sql}) as temp_table where row_num {$constraint} order by row_num";
326-
}
327-
328256
/**
329257
* Compile the limit / offset row constraint for a query.
330258
*
@@ -381,6 +309,10 @@ public function compileRandom($seed)
381309
*/
382310
protected function compileLimit(Builder $query, $limit)
383311
{
312+
if($limit && $query->offset > 0){
313+
return "fetch next {$limit} rows only";
314+
}
315+
384316
return '';
385317
}
386318

@@ -393,6 +325,10 @@ protected function compileLimit(Builder $query, $limit)
393325
*/
394326
protected function compileOffset(Builder $query, $offset)
395327
{
328+
if($offset){
329+
return "offset {$offset} rows";
330+
}
331+
396332
return '';
397333
}
398334

tests/Database/DatabaseQueryBuilderTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3502,19 +3502,18 @@ public function testSqlServerLimitsAndOffsets()
35023502
$this->assertSame('select top 10 * from [users]', $builder->toSql());
35033503

35043504
$builder = $this->getSqlServerBuilder();
3505-
$builder->select('*')->from('users')->skip(10);
3506-
$this->assertSame('select * from (select *, row_number() over (order by (select 0)) as row_num from [users]) as temp_table where row_num >= 11 order by row_num', $builder->toSql());
3505+
$builder->select('*')->from('users')->skip(10)->orderBy('email', 'desc');
3506+
$this->assertSame('select * from [users] order by [email] desc offset 10 rows', $builder->toSql());
35073507

35083508
$builder = $this->getSqlServerBuilder();
35093509
$builder->select('*')->from('users')->skip(10)->take(10);
3510-
$this->assertSame('select * from (select *, row_number() over (order by (select 0)) as row_num from [users]) as temp_table where row_num between 11 and 20 order by row_num', $builder->toSql());
3510+
$this->assertSame('select * from [users] order by (SELECT 0) offset 10 rows fetch next 10 rows only', $builder->toSql());
35113511

35123512
$builder = $this->getSqlServerBuilder();
35133513
$builder->select('*')->from('users')->skip(11)->take(10)->orderBy('email', 'desc');
35143514
$this->assertSame('select * from [users] order by [email] desc offset 11 rows fetch next 10 rows only', $builder->toSql());
35153515

35163516
$builder = $this->getSqlServerBuilder();
3517-
$subQueryBuilder = $this->getSqlServerBuilder();
35183517
$subQuery = function ($query) {
35193518
return $query->select('created_at')->from('logins')->where('users.name', 'nameBinding')->whereColumn('user_id', 'users.id')->limit(1);
35203519
};

0 commit comments

Comments
 (0)