-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #20239: fix yii\data\ActiveDataProvider
to avoid unexpected pagination results with UNION queries
#20311
base: master
Are you sure you want to change the base?
Conversation
…ted pagination results with UNION queries
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #20311 +/- ##
============================================
+ Coverage 64.84% 64.86% +0.01%
- Complexity 11433 11435 +2
============================================
Files 431 431
Lines 37187 37197 +10
============================================
+ Hits 24115 24126 +11
+ Misses 13072 13071 -1 ☔ View full report in Codecov by Sentry. |
Let's exclude |
@rob006 would you please take a look? |
{ | ||
if (!$this->query instanceof QueryInterface) { | ||
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); | ||
} | ||
$query = clone $this->query; | ||
$wrapper = clone $this->query; | ||
if ($wrapper instanceof Query && !empty($wrapper->union)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we return $this->query
if the condition isn't met right away?
if (!$wrapper instanceof Query || empty($wrapper->union)) {
return $this->query;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather return $wrapper;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to keep the original logic (not reusing the original query):
yii2/framework/data/ActiveDataProvider.php
Lines 98 to 104 in b0b7832
protected function prepareModels() | |
{ | |
if (!$this->query instanceof QueryInterface) { | |
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); | |
} | |
$query = clone $this->query; | |
if (($pagination = $this->getPagination()) !== false) { |
yii2/framework/data/ActiveDataProvider.php
Lines 162 to 168 in b0b7832
protected function prepareTotalCount() | |
{ | |
if (!$this->query instanceof QueryInterface) { | |
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); | |
} | |
$query = clone $this->query; | |
return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db); |
Should I change it?
$wrapper->where = []; | ||
$wrapper->limit = null; | ||
$wrapper->offset = null; | ||
$wrapper->orderBy = []; | ||
$wrapper->selectOption = null; | ||
$wrapper->distinct = false; | ||
$wrapper->groupBy = []; | ||
$wrapper->join = []; | ||
$wrapper->having = []; | ||
$wrapper->union = []; | ||
$wrapper->params = []; | ||
$wrapper->withQueries = []; | ||
$wrapper->select('*')->from(['q' => $this->query]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be more properties that needs to be reset. For example ActiveQuery
has on
and joinWith
(and sql
?) properties, that may leak to wrapper. Developer may extend these classes and create own properties with the same problem. Query
needs some kind of reset()
method, that would be responsible for resting query state.
Or we cold use new Query()
, similar to ActiveQuery::queryScalar()
:
yii2/framework/db/ActiveQuery.php
Line 355 in b0b7832
$command = (new Query())->select([$selectExpression]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a new Query()
will break prepareModels()
itself if query
is an ActiveQuery
and the expected return value of ActiveQuery::all()
is array of objects rather then an array of arrays. I'll think about the solution a little more.
I don't really like the implementation, it looks like a workaround. The design of the query build doesn't allow for a more proper solution without breaking BC. Or am I missing something? |
{ | ||
if (!$this->query instanceof QueryInterface) { | ||
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); | ||
} | ||
$query = clone $this->query; | ||
if (!$this->query instanceof Query || empty($this->query->union)) { | ||
return clone $this->query; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a clone here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests pass successfully without clone. I think it can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this kind of test will fail without clone.
$provider = new ActiveDataProvider([
'query' => Item::find(),
]);
$pagination = $provider->getPagination();
$pagination->pageSize = 2;
$provider->prepare();
$this->assertCount(2, $provider->getModels());
$provider->setPagination(false);
$provider->prepare(true);
$this->assertCount(5, $provider->getModels());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
Related PR: #20246