diff --git a/src/Query.php b/src/Query.php index 7bd5b9a..b3c2f1c 100644 --- a/src/Query.php +++ b/src/Query.php @@ -647,7 +647,8 @@ public function derive($relation, Model $source): static $query = $relation->getTargetClass()::on($this->getDb()); $resolver = $query->getResolver(); - $reversed = $relation->reverse($resolver); + $reversed = $relation->reverse($resolver) + ->setJoinType('INNER'); // This will fail if the name ("self") is occupied, but that's fine… $resolver->getRelations($query->getModel())->add($reversed); @@ -724,6 +725,10 @@ public function createSubQuery(Model $target, string $targetPath, ?Model $from = $oppositeRelation->setName($predecessor); } + // Always override the join type, as a sub-query that is unable + // to establish a link to the outer query is useless anyway. + $oppositeRelation->setJoinType('INNER'); + $relations = new Relations(); $relations->add($oppositeRelation); foreach ($subQueryResolver->getRelations($previousHop) as $detour) { diff --git a/tests/Lib/Model/Chair.php b/tests/Lib/Model/Chair.php index ff32637..33039bf 100644 --- a/tests/Lib/Model/Chair.php +++ b/tests/Lib/Model/Chair.php @@ -28,6 +28,7 @@ public function getColumns() public function createRelations(Relations $relations) { - $relations->hasOne('employee', Employee::class); + $relations->hasOne('employee', Employee::class) + ->setJoinType('LEFT'); } } diff --git a/tests/QueryTest.php b/tests/QueryTest.php index e071323..47e9d23 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -6,9 +6,11 @@ use ipl\Orm\NoopQuery; use ipl\Orm\Query; use ipl\Orm\ResolvedExpression; +use ipl\Sql\Connection; use ipl\Sql\Expression; use ipl\Sql\Test\TestCase; use ipl\Stdlib\Filter; +use ipl\Tests\Orm\Lib\Model\Employee; class QueryTest extends TestCase { @@ -652,4 +654,26 @@ public function testDeriveReturnsAValidButEmptyQueryIfNoLinkIsPossible(): void $this->assertEmpty(iterator_to_array($profile)); $this->assertNull($profile->first()); } + + public function testSubQueriesOverrideTheJoinType(): void + { + $query = (new Query()) + ->setDb($this->createStub(Connection::class)) + ->setModel(new Employee()) + ->columns('name') + ->filter(Filter::equal('chair.vendor', 'Icinga')); + + $this->assertSql( + <<<'SQL' +SELECT employee.name FROM employee +WHERE (employee.deleted = ?) AND (employee.id IN ((SELECT sub_chair_employee.id AS sub_chair_employee_id + FROM chair sub_chair + INNER JOIN employee sub_chair_employee + ON (sub_chair_employee.id = sub_chair.employee_id) AND (sub_chair_employee.deleted = ?) + WHERE sub_chair.vendor = ?))) +SQL, + $query->assembleSelect(), + ['n', 'n', 'Icinga'] + ); + } } diff --git a/tests/RelationTest.php b/tests/RelationTest.php index d76351f..035e218 100644 --- a/tests/RelationTest.php +++ b/tests/RelationTest.php @@ -307,11 +307,11 @@ public function testADeclaredInverseRelationCanBeReusedDuringReverse() <<<'SQL' SELECT relationship.id, relationship.coupler FROM relationship - LEFT JOIN loose relationship_self ON relationship_self.coupler = relationship.coupler + INNER JOIN loose relationship_self ON relationship_self.coupler = relationship.coupler WHERE (relationship_self.coupler = ?) AND ((relationship.id NOT IN ((SELECT sub_loose_relationship.id AS sub_loose_relationship_id FROM loose sub_loose - LEFT JOIN relationship sub_loose_relationship ON sub_loose_relationship.coupler = sub_loose.coupler + INNER JOIN relationship sub_loose_relationship ON sub_loose_relationship.coupler = sub_loose.coupler WHERE (sub_loose.id = ?) AND (sub_loose_relationship.id IS NOT NULL) GROUP BY sub_loose_relationship.id HAVING COUNT(DISTINCT sub_loose.id) >= ?)) OR relationship.id IS NULL)) diff --git a/tests/VisibilityFilterTest.php b/tests/VisibilityFilterTest.php index 03cdff4..6b040ae 100644 --- a/tests/VisibilityFilterTest.php +++ b/tests/VisibilityFilterTest.php @@ -367,7 +367,7 @@ public function testDeriveAppliesTheModelVisibilityFilterAndTheRelationFilter() 'SELECT employee.id, employee.name, employee.active, employee.deleted,' . ' employee.role, employee.department_id, employee.office_id' . ' FROM employee' - . ' LEFT JOIN department employee_self' + . ' INNER JOIN department employee_self' . ' ON (employee_self.id = employee.department_id) AND (employee.active = ?)' . ' WHERE (employee.deleted = ?) AND (employee_self.id = ?)', $derived->assembleSelect(),