From 166dd442a7b18d3405b0fbdac44d729909b814ac Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 5 Nov 2025 13:45:16 +0100 Subject: [PATCH] FilterProcessor: Wrap multiple count distinct targets in parantheses if PostgreSQL is in use. MySQL does not require it and even complains if used. fixes #151 --- src/Compat/FilterProcessor.php | 17 ++++++++++------- tests/FilterProcessorTest.php | 20 ++++++++++++++++++++ tests/Lib/Model/Chair.php | 33 +++++++++++++++++++++++++++++++++ tests/Lib/Model/Employee.php | 1 + 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 tests/Lib/Model/Chair.php diff --git a/src/Compat/FilterProcessor.php b/src/Compat/FilterProcessor.php index 361aabb..1b3aab9 100644 --- a/src/Compat/FilterProcessor.php +++ b/src/Compat/FilterProcessor.php @@ -9,6 +9,7 @@ use ipl\Orm\Query; use ipl\Orm\Relation; use ipl\Orm\UnionQuery; +use ipl\Sql\Adapter\Pgsql; use ipl\Sql\Filter\Exists; use ipl\Sql\Filter\In; use ipl\Sql\Filter\NotExists; @@ -323,15 +324,17 @@ protected function requireAndResolveFilterColumns(Filter\Rule $filter, Query $qu $subQuerySelect = $subQuery->assembleSelect()->resetOrderBy(); if ($count !== null && ($negate || $filter instanceof Filter\All)) { - $targetKeys = join( - ',', - array_values( - $subQuery->getResolver()->qualifyColumns( - (array) $subQuery->getModel()->getKeyName(), - $subQuery->getModel() - ) + $targetKeys = array_values( + $subQuery->getResolver()->qualifyColumns( + (array) $subQuery->getModel()->getKeyName(), + $subQuery->getModel() ) ); + if (count($targetKeys) > 1 && $query->getDb()->getAdapter() instanceof Pgsql) { + $targetKeys = '(' . join(', ', $targetKeys) . ')'; + } else { + $targetKeys = join(', ', $targetKeys); + } $subQuerySelect->having(["COUNT(DISTINCT $targetKeys) >= ?" => $count]); $subQuerySelect->groupBy(array_values($subQuerySelect->getColumns())); diff --git a/tests/FilterProcessorTest.php b/tests/FilterProcessorTest.php index e91c2a6..6049e0d 100644 --- a/tests/FilterProcessorTest.php +++ b/tests/FilterProcessorTest.php @@ -7,6 +7,7 @@ use ipl\Sql\Connection; use ipl\Sql\Test\Databases; use ipl\Stdlib\Filter; +use ipl\Tests\Orm\Lib\Model\Employee; use ipl\Tests\Orm\Lib\Model\Office; class FilterProcessorTest extends \PHPUnit\Framework\TestCase @@ -116,6 +117,23 @@ public function testUnequalTargetingAnOptionalToManyRelationIgnoresFalsePositive $this->assertSame('London', $results[0]['city'] ?? 'not found'); } + /** @dataProvider databases */ + public function testNegationOfAToManyRelationWorksAcrossDatabaseAdapters(Connection $db): void + { + $db->insert('employee', ['id' => 1, 'department_id' => 1, 'name' => 'Minnie', 'role' => 'CEO']); + $db->insert('employee', ['id' => 2, 'department_id' => 2, 'name' => 'Goofy', 'role' => 'Developer']); + $db->insert('chair', ['department_id' => 1, 'employee_id' => 1, 'vendor' => 'Acme Chairs']); + $db->insert('chair', ['department_id' => 2, 'employee_id' => 1, 'vendor' => 'Bcme Chairs']); + $db->insert('chair', ['department_id' => 3, 'employee_id' => 2, 'vendor' => 'Bcme Chairs']); + + $employeesWithoutAcmeChairs = Employee::on($db) + ->filter(Filter::unequal('chair.vendor', 'Acme Chairs')); + $results = iterator_to_array($employeesWithoutAcmeChairs); + + $this->assertCount(1, $results); + $this->assertSame('Goofy', $results[0]->name); + } + protected function createSchema(Connection $db, string $driver): void { $db->exec('CREATE TABLE office (id INT PRIMARY KEY, city VARCHAR(255))'); @@ -124,10 +142,12 @@ protected function createSchema(Connection $db, string $driver): void 'CREATE TABLE employee (id INT PRIMARY KEY, department_id INT,' . ' office_id INT, name VARCHAR(255), role VARCHAR(255))' ); + $db->exec('CREATE TABLE chair (department_id INT, employee_id INT, vendor VARCHAR(255))'); } protected function dropSchema(Connection $db, string $driver): void { + $db->exec('DROP TABLE IF EXISTS chair'); $db->exec('DROP TABLE IF EXISTS employee'); $db->exec('DROP TABLE IF EXISTS department'); $db->exec('DROP TABLE IF EXISTS office'); diff --git a/tests/Lib/Model/Chair.php b/tests/Lib/Model/Chair.php new file mode 100644 index 0000000..f5e6ab2 --- /dev/null +++ b/tests/Lib/Model/Chair.php @@ -0,0 +1,33 @@ +belongsTo('employee', Employee::class); + } +} diff --git a/tests/Lib/Model/Employee.php b/tests/Lib/Model/Employee.php index 8ee65c0..629a770 100644 --- a/tests/Lib/Model/Employee.php +++ b/tests/Lib/Model/Employee.php @@ -32,5 +32,6 @@ public function createRelations(Relations $relations) $relations->belongsTo('department', Department::class); $relations->belongsTo('office', Office::class) ->setJoinType('LEFT'); + $relations->hasMany('chair', Chair::class); } }