Browse Source

Fix pagination ordering on calculated columns on SQL Server.

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.
Mike Fellows 8 years ago
parent
commit
2759482cbd
1 changed files with 27 additions and 1 deletions
  1. 27 1
      src/Database/Dialect/SqlserverDialectTrait.php

+ 27 - 1
src/Database/Dialect/SqlserverDialectTrait.php

@@ -14,6 +14,7 @@
  */
 namespace Cake\Database\Dialect;
 
+use Cake\Database\ExpressionInterface;
 use Cake\Database\Expression\FunctionExpression;
 use Cake\Database\Expression\OrderByExpression;
 use Cake\Database\Expression\UnaryExpression;
@@ -21,6 +22,7 @@ use Cake\Database\Query;
 use Cake\Database\Schema\SqlserverSchema;
 use Cake\Database\SqlDialectTrait;
 use Cake\Database\SqlserverCompiler;
+use Cake\Database\ValueBinder;
 use PDO;
 
 /**
@@ -102,7 +104,31 @@ trait SqlserverDialectTrait
     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([