Skip to content
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

Update ActiveDataProvider::prepareModels() to avoid SQL error with UNION queries #20246

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

cepaim
Copy link

@cepaim cepaim commented Aug 16, 2024

Q A
Is bugfix?
New feature? ✔️
Breaks BC?
Fixed issues #20239

…ION queries

As for issue yiisoft#20239, move order by and limit clauses to the last UNION query to avoid SQL errors on UNION queries
Update ActiveDataProvider::prepareModels() to avoid SQL error with UNION queries
Copy link

codecov bot commented Aug 16, 2024

Codecov Report

Attention: Patch coverage is 50.00000% with 4 lines in your changes missing coverage. Please review.

Project coverage is 64.93%. Comparing base (34d2396) to head (a1807f9).
Report is 19 commits behind head on master.

Files with missing lines Patch % Lines
framework/data/ActiveDataProvider.php 50.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #20246      +/-   ##
============================================
- Coverage     64.94%   64.93%   -0.01%     
- Complexity    11390    11394       +4     
============================================
  Files           430      430              
  Lines         36912    36919       +7     
============================================
+ Hits          23972    23975       +3     
- Misses        12940    12944       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rob006
Copy link
Contributor

rob006 commented Aug 16, 2024

I don't think this is a correct solution. IMO it should be fixed in QueryBuilder::build():

public function build($query, $params = [])
{
$query = $query->prepare($this);
$params = empty($params) ? $query->params : array_merge($params, $query->params);
$clauses = [
$this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
$this->buildFrom($query->from, $params),
$this->buildJoin($query->join, $params),
$this->buildWhere($query->where, $params),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having, $params),
];
$sql = implode($this->separator, array_filter($clauses));
$sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
if (!empty($query->orderBy)) {
foreach ($query->orderBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
}
}
}
if (!empty($query->groupBy)) {
foreach ($query->groupBy as $expression) {
if ($expression instanceof ExpressionInterface) {
$this->buildExpression($expression, $params);
}
}
}
$union = $this->buildUnion($query->union, $params);
if ($union !== '') {
$sql = "($sql){$this->separator}$union";
}
$with = $this->buildWithQueries($query->withQueries, $params);
if ($with !== '') {
$sql = "$with{$this->separator}$sql";
}
return [$sql, $params];
}

$this->buildOrderByAndLimit() should be called after handing union queries.

@cepaim
Copy link
Author

cepaim commented Aug 16, 2024

@rob006 You are right!

I don't know Yii2 so deeply, but if we fix this at the query level, it will be fixed for all its uses.

At the moment, my hack is working for me and I am now very busy. I'll try to find time to make another PR.

I think this PR can be rejected and closed.

@rob006
Copy link
Contributor

rob006 commented Aug 16, 2024

@cepaim Does it really work for MySQL? Because only SQLite doesn't have parentheses, for all other DBMS generated query will look like (SELECT * FROM a) UNION (SELECT * FROM b LIMIT 10) which also does not look right (it should be (SELECT * FROM a) UNION (SELECT * FROM b) LIMIT 10?).

@cepaim
Copy link
Author

cepaim commented Aug 16, 2024

@rob006
I haven't tested it on mysql, only on sqlite3.

I'll tell you when I test in on mysql.

@samdark samdark requested review from a team August 16, 2024 18:11
@@ -101,17 +101,29 @@ protected function prepareModels()
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;

$has_union = $query->union && count($query->union);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$has_union = $query->union && count($query->union);
$hasUnion = $query->union && count($query->union);

if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount();
if ($pagination->totalCount === 0) {
return [];
}
$query->limit($pagination->getLimit())->offset($pagination->getOffset());
if ($has_union) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($has_union) {
if ($hasUnion) {

}
if (($sort = $this->getSort()) !== false) {
$query->addOrderBy($sort->getOrders());
}
if ($has_union) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($has_union) {
if ($hasUnion) {

@mtangoo
Copy link
Contributor

mtangoo commented Sep 21, 2024

At the moment, my hack is working for me and I am now very busy. I'll try to find time to make another PR.

So can we close this one and wait for the other one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants