Browse Source

Merge pull request #10947 from MCF/fix_paginate_order_by

Fix pagination ordering on calculated columns on SQL Server.
José Lorenzo Rodríguez 8 years ago
parent
commit
c75f2e7336

+ 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')) {
+            // SQL server does not support column aliases in OVER clauses.  But
+            // the only practical way to specify the use of calculated columns
+            // is with their alias.  So substitute the select SQL in place of
+            // any column aliases for those entries in the order clause.
+            $select = $original->clause('select');
+            $order = new OrderByExpression();
+            $original
+                ->clause('order')
+                ->iterateParts(function ($direction, $orderBy) use ($select, $order) {
+                    $key = $orderBy;
+                    if (isset($select[$orderBy]) &&
+                        $select[$orderBy] instanceof ExpressionInterface
+                    ) {
+                        $key = $select[$orderBy]->sql(new ValueBinder());
+                    }
+                    $order->add([$key => $direction]);
+
+                    // Leave original order clause unchanged.
+                    return $orderBy;
+                });
+        } else {
+            $order = new OrderByExpression('(SELECT NULL)');
+        }
 
         $query = clone $original;
         $query->select([

+ 30 - 0
tests/TestCase/Database/QueryTest.php

@@ -2248,6 +2248,36 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Test selecting rows using the page() method and ordering the results
+     * by a calculated column.
+     *
+     * @return void
+     */
+    public function testSelectPageWithOrder()
+    {
+        $this->loadFixtures('Comments');
+        $query = new Query($this->connection);
+        $result = $query
+            ->select([
+                'id',
+                'ids_added' => $query->newExpr()->add('(user_id + article_id)')
+            ])
+            ->from('comments')
+            ->order(['ids_added' => 'asc'])
+            ->limit(2)
+            ->page(3)
+            ->execute();
+        $this->assertCount(2, $result);
+        $this->assertEquals(
+            [
+                ['id' => '6', 'ids_added' => '4'],
+                ['id' => '2', 'ids_added' => '5']
+            ],
+            $result->fetchAll('assoc')
+        );
+    }
+
+    /**
      * Tests that Query objects can be included inside the select clause
      * and be used as a normal field, including binding any passed parameter
      *