Skip to content

Commit

Permalink
Fix pagination ordering on calculated columns on SQL Server.
Browse files Browse the repository at this point in the history
On older versions of SQL Server, the pagination is accomplished using a
ROW_NUMBER() OVER clause.  Unfortunately, OVER does not support the use of
column aliases.  But for calculated columns the only practical way to
specify their use in ordering is with their alias, this currently causes a
SQL error.

This fix will substitute the calculation's SQL syntax in place of the
column alias before generating the OVER clause for calculated columns,
fixing the bug.
  • Loading branch information
Mike Fellows authored and Mike Fellows committed Jul 25, 2017
1 parent d254ead commit 2759482
Showing 1 changed file with 27 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/Database/Dialect/SqlserverDialectTrait.php
Expand Up @@ -14,13 +14,15 @@
*/
namespace Cake\Database\Dialect;

use Cake\Database\ExpressionInterface;
use Cake\Database\Expression\FunctionExpression;
use Cake\Database\Expression\OrderByExpression;
use Cake\Database\Expression\UnaryExpression;
use Cake\Database\Query;
use Cake\Database\Schema\SqlserverSchema;
use Cake\Database\SqlDialectTrait;
use Cake\Database\SqlserverCompiler;
use Cake\Database\ValueBinder;
use PDO;

/**
Expand Down Expand Up @@ -102,7 +104,31 @@ public function _version()
protected function _pagingSubquery($original, $limit, $offset)
{
$field = '_cake_paging_._cake_page_rownum_';
$order = $original->clause('order') ?: new OrderByExpression('(SELECT NULL)');

if ($original->clause('order')) {
$order = clone $original->clause('order');
} else {
$order = new OrderByExpression('(SELECT NULL)');
}

// SQL server does not support column aliases in OVER clauses. But for
// calculated columns the alias is the only practical identifier to use
// when specifying the order. So if a column alias is specified in the
// order clause, and the value of that alias is an expression, change
// the alias into what it represents by setting the clause's key to be
// the SQL representation of its value. The UnaryExpression creation
// below will then do the right thing and use the calculation in the
// ROW_NUMBER() OVER clause instead of the alias.
$select = $original->clause('select');
$order->iterateParts(function ($direction, &$orderBy) use ($select) {
if (isset($select[$orderBy])) {
if ($select[$orderBy] instanceof ExpressionInterface) {
$orderBy = $select[$orderBy]->sql(new ValueBinder());
}
}

return $direction;
});

$query = clone $original;
$query->select([
Expand Down

0 comments on commit 2759482

Please sign in to comment.