Browse Source

Clone queries during pagination.

While I don't think the SQL errors coming from EagerLoader re-use can be
fixed without invasive changes to `EagerLoader`, we can treat the
symptom of PaginatorComponent triggering this scenario by pre-emptively
cloning the query.

Refs #10697
Mark Story 8 years ago
parent
commit
131c2d8b66

+ 2 - 1
src/Controller/Component/PaginatorComponent.php

@@ -187,9 +187,10 @@ class PaginatorComponent extends Component
             $query->applyOptions($options);
         }
 
+        $cleanQuery = clone $query;
         $results = $query->all();
         $numResults = count($results);
-        $count = $numResults ? $query->count() : 0;
+        $count = $numResults ? $cleanQuery->count() : 0;
 
         $defaults = $this->getDefaults($alias, $settings);
         unset($defaults[0]);

+ 33 - 1
tests/TestCase/Controller/Component/PaginatorComponentTest.php

@@ -19,6 +19,7 @@ use Cake\Controller\Component\PaginatorComponent;
 use Cake\Controller\Controller;
 use Cake\Core\Configure;
 use Cake\Datasource\ConnectionManager;
+use Cake\Datasource\EntityInterface;
 use Cake\Http\ServerRequest;
 use Cake\Network\Exception\NotFoundException;
 use Cake\ORM\Entity;
@@ -47,7 +48,10 @@ class PaginatorComponentTest extends TestCase
      *
      * @var array
      */
-    public $fixtures = ['core.posts'];
+    public $fixtures = [
+        'core.posts', 'core.articles', 'core.articles_tags',
+        'core.authors', 'core.authors_tags', 'core.tags'
+    ];
 
     /**
      * Don't load data for fixtures for all tests
@@ -203,6 +207,34 @@ class PaginatorComponentTest extends TestCase
     }
 
     /**
+     * Test that nested eager loaders don't trigger invalid SQL errors.
+     *
+     * @return void
+     */
+    public function testPaginateNestedEagerLoader()
+    {
+        $this->loadFixtures('Articles', 'Tags', 'Authors', 'ArticlesTags', 'AuthorsTags');
+        $articles = TableRegistry::get('Articles');
+        $articles->belongsToMany('Tags');
+        $tags = TableRegistry::get('Tags');
+        $tags->belongsToMany('Authors');
+
+        $articles->eventManager()->on('Model.beforeFind', function($event, $query) {
+            $query ->matching('Tags', function ($q) {
+                return $q->matching('Authors', function ($q) {
+                    return $q->where(['Authors.name' => 'larry']);
+                });
+            });
+        });
+        $results = $this->Paginator->paginate($articles, []);
+
+        $result = $results->first();
+        $this->assertInstanceOf(EntityInterface::class, $result);
+        $this->assertInstanceOf(EntityInterface::class, $result->_matchingData['Tags']);
+        $this->assertInstanceOf(EntityInterface::class, $result->_matchingData['Authors']);
+    }
+
+    /**
      * test that flat default pagination parameters work.
      *
      * @return void