From 974682df24150f60ea1d14328f210ce500162bdd Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Wed, 10 Apr 2024 18:53:30 +0200 Subject: [PATCH] add workaround for same-named columns in GROUP BY for MySQL [closes #662] --- src/Collection/DbalCollection.php | 33 +++++++++++++++++ .../Collection/collection.having.phpt | 36 +++++++++++++++++++ ...estHavingWithSameNamedColumnsInGroupBy.sql | 1 + 3 files changed, 70 insertions(+) create mode 100644 tests/cases/integration/Collection/collection.having.phpt create mode 100644 tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionHavingTest_testHavingWithSameNamedColumnsInGroupBy.sql diff --git a/src/Collection/DbalCollection.php b/src/Collection/DbalCollection.php index f98b1a1a..3e06cbf3 100644 --- a/src/Collection/DbalCollection.php +++ b/src/Collection/DbalCollection.php @@ -5,6 +5,8 @@ use Iterator; use Nextras\Dbal\IConnection; +use Nextras\Dbal\Platforms\Data\Fqn; +use Nextras\Dbal\Platforms\MySqlPlatform; use Nextras\Dbal\QueryBuilder\QueryBuilder; use Nextras\Orm\Collection\Expression\ExpressionContext; use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult; @@ -319,6 +321,9 @@ public function getQueryBuilder(): QueryBuilder } else { $this->queryBuilder->andWhere($expression->expression, ...$expression->args); } + if ($this->mapper->getDatabasePlatform()->getName() === MySqlPlatform::NAME) { + $this->applyGroupByWithSameNamedColumnsWorkaround($this->queryBuilder, $groupBy); + } $this->filtering = []; } @@ -400,4 +405,32 @@ protected function getHelper(): DbalQueryBuilderHelper return $this->helper; } + + + /** + * Apply workaround for MySQL that is not able to properly resolve columns when there are more same-named + * columns in the GROUP BY clause, even though they are properly referenced to their tables. Orm workarounds + * this by adding them to the SELECT clause and renames them not to conflict anywhere. + * + * @param list $groupBy + */ + private function applyGroupByWithSameNamedColumnsWorkaround(QueryBuilder $queryBuilder, array $groupBy): void + { + $map = []; + foreach ($groupBy as $fqn) { + if (!isset($map[$fqn->name])) { + $map[$fqn->name] = [$fqn]; + } else { + $map[$fqn->name][] = $fqn; + } + } + $i = 0; + foreach ($map as $fqns) { + if (count($fqns) > 1) { + foreach ($fqns as $fqn) { + $queryBuilder->addSelect("%column AS __nextras_fix_" . $i++, $fqn); // @phpstan-ignore-line + } + } + } + } } diff --git a/tests/cases/integration/Collection/collection.having.phpt b/tests/cases/integration/Collection/collection.having.phpt new file mode 100644 index 00000000..74d3e47f --- /dev/null +++ b/tests/cases/integration/Collection/collection.having.phpt @@ -0,0 +1,36 @@ +orm->books->findBy([ + ICollection::OR, + 'tags->id' => 1, + 'author->name' => 'Writer 1', + 'publisher->name' => 'Nextras publisher A', + ]); + Assert::same($books->count(), 3); + } +} + + +$test = new CollectionHavingTest(); +$test->run(); diff --git a/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionHavingTest_testHavingWithSameNamedColumnsInGroupBy.sql b/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionHavingTest_testHavingWithSameNamedColumnsInGroupBy.sql new file mode 100644 index 00000000..e9374acc --- /dev/null +++ b/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionHavingTest_testHavingWithSameNamedColumnsInGroupBy.sql @@ -0,0 +1 @@ +SELECT "books".* FROM "books" AS "books" LEFT JOIN "books_x_tags" AS "books_x_tags" ON ("books"."id" = "books_x_tags"."book_id") LEFT JOIN "tags" AS "tags_any" ON (("books_x_tags"."tag_id" = "tags_any"."id") AND "tags_any"."id" = 1) LEFT JOIN "public"."authors" AS "author" ON ("books"."author_id" = "author"."id") LEFT JOIN "publishers" AS "publisher" ON ("books"."publisher_id" = "publisher"."publisher_id") GROUP BY "books"."id", "author"."name", "publisher"."name" HAVING ((COUNT("tags_any"."id") > 0) OR ("author"."name" = 'Writer 1') OR ("publisher"."name" = 'Nextras publisher A'));