Skip to content

Commit 91250d9

Browse files
[12.x] fix: one of many subquery constraints (#55168)
* fix: one of many subquery constraints * fix: backout Relation::addConstraints signature change * Update HasOneThrough.php * Update HasOneOrManyThrough.php --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 0b0a274 commit 91250d9

File tree

6 files changed

+28
-14
lines changed

6 files changed

+28
-14
lines changed

src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null)
131131
];
132132
}
133133

134+
$this->addConstraints();
135+
134136
$columns = $this->query->getQuery()->columns;
135137

136138
if (is_null($columns) || $columns === ['*']) {

src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,14 @@ public function __construct(Builder $query, Model $farParent, Model $throughPare
9797
*/
9898
public function addConstraints()
9999
{
100+
$query = $this->getRelationQuery();
101+
100102
$localValue = $this->farParent[$this->localKey];
101103

102-
$this->performJoin();
104+
$this->performJoin($query);
103105

104106
if (static::$constraints) {
105-
$this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
107+
$query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
106108
}
107109
}
108110

@@ -114,7 +116,7 @@ public function addConstraints()
114116
*/
115117
protected function performJoin(?Builder $query = null)
116118
{
117-
$query = $query ?: $this->query;
119+
$query ??= $this->query;
118120

119121
$farKey = $this->getQualifiedFarKeyName();
120122

src/Illuminate/Database/Eloquent/Relations/HasOneThrough.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ public function addOneOfManySubQueryConstraints(Builder $query, $column = null,
7878
{
7979
$query->addSelect([$this->getQualifiedFirstKeyName()]);
8080

81-
$this->performJoin($query);
81+
// We need to join subqueries that aren't the inner-most subquery which is joined in the CanBeOneOfMany::ofMany method...
82+
if ($this->getOneOfManySubQuery() !== null) {
83+
$this->performJoin($query);
84+
}
8285
}
8386

8487
/** @inheritDoc */

tests/Database/DatabaseEloquentHasOneOfManyTest.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,19 @@ public function testRelationNameCanBeSet()
9999
$this->assertSame('baz', $relation->getRelationName());
100100
}
101101

102+
public function testCorrectLatestOfManyQuery(): void
103+
{
104+
$user = HasOneOfManyTestUser::create();
105+
$relation = $user->latest_login();
106+
$this->assertSame('select "logins".* from "logins" inner join (select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" = ? and "logins"."user_id" is not null group by "logins"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "logins"."user_id" where "logins"."user_id" = ? and "logins"."user_id" is not null', $relation->getQuery()->toSql());
107+
}
108+
102109
public function testEagerLoadingAppliesConstraintsToInnerJoinSubQuery()
103110
{
104111
$user = HasOneOfManyTestUser::create();
105112
$relation = $user->latest_login();
106113
$relation->addEagerConstraints([$user]);
107-
$this->assertSame('select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" in (1) group by "logins"."user_id"', $relation->getOneOfManySubQuery()->toSql());
114+
$this->assertSame('select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" = ? and "logins"."user_id" is not null and "logins"."user_id" in (1) group by "logins"."user_id"', $relation->getOneOfManySubQuery()->toSql());
108115
}
109116

110117
public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalScope()
@@ -116,7 +123,7 @@ public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalSco
116123
$user = HasOneOfManyTestUser::create();
117124
$relation = $user->latest_login_without_global_scope();
118125
$relation->addEagerConstraints([$user]);
119-
$this->assertSame('select "logins".* from "logins" inner join (select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" in (1) group by "logins"."user_id") as "latestOfMany" on "latestOfMany"."id_aggregate" = "logins"."id" and "latestOfMany"."user_id" = "logins"."user_id" where "logins"."user_id" = ? and "logins"."user_id" is not null', $relation->getQuery()->toSql());
126+
$this->assertSame('select "logins".* from "logins" inner join (select MAX("logins"."id") as "id_aggregate", "logins"."user_id" from "logins" where "logins"."user_id" = ? and "logins"."user_id" is not null and "logins"."user_id" in (1) group by "logins"."user_id") as "latestOfMany" on "latestOfMany"."id_aggregate" = "logins"."id" and "latestOfMany"."user_id" = "logins"."user_id" where "logins"."user_id" = ? and "logins"."user_id" is not null', $relation->getQuery()->toSql());
120127

121128
HasOneOfManyTestLogin::addGlobalScope('test', function ($query) {
122129
});
@@ -130,7 +137,7 @@ public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalSco
130137

131138
$user = HasOneOfManyTestUser::create();
132139
$relation = $user->price_without_global_scope();
133-
$this->assertSame('select "prices".* from "prices" inner join (select max("prices"."id") as "id_aggregate", min("prices"."published_at") as "published_at_aggregate", "prices"."user_id" from "prices" inner join (select max("prices"."published_at") as "published_at_aggregate", "prices"."user_id" from "prices" where "published_at" < ? group by "prices"."user_id") as "price_without_global_scope" on "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "prices"."user_id" where "published_at" < ? group by "prices"."user_id") as "price_without_global_scope" on "price_without_global_scope"."id_aggregate" = "prices"."id" and "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "prices"."user_id" where "prices"."user_id" = ? and "prices"."user_id" is not null', $relation->getQuery()->toSql());
140+
$this->assertSame('select "prices".* from "prices" inner join (select max("prices"."id") as "id_aggregate", min("prices"."published_at") as "published_at_aggregate", "prices"."user_id" from "prices" inner join (select max("prices"."published_at") as "published_at_aggregate", "prices"."user_id" from "prices" where "published_at" < ? and "prices"."user_id" = ? and "prices"."user_id" is not null group by "prices"."user_id") as "price_without_global_scope" on "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "prices"."user_id" where "published_at" < ? group by "prices"."user_id") as "price_without_global_scope" on "price_without_global_scope"."id_aggregate" = "prices"."id" and "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "prices"."user_id" where "prices"."user_id" = ? and "prices"."user_id" is not null', $relation->getQuery()->toSql());
134141

135142
HasOneOfManyTestPrice::addGlobalScope('test', function ($query) {
136143
});
@@ -591,7 +598,7 @@ public function states()
591598
public function foo_state()
592599
{
593600
return $this->hasOne(HasOneOfManyTestState::class, 'user_id')->ofMany(
594-
['id' => 'max'],
601+
[], // should automatically add 'id' => 'max'
595602
function ($q) {
596603
$q->where('type', 'foo');
597604
}

tests/Database/DatabaseEloquentHasOneThroughOfManyTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,23 @@ public function testCorrectLatestOfManyQuery(): void
9898
{
9999
$user = HasOneThroughOfManyTestUser::create();
100100
$relation = $user->latest_login();
101-
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
101+
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
102102
}
103103

104104
public function testEagerLoadingAppliesConstraintsToInnerJoinSubQuery(): void
105105
{
106106
$user = HasOneThroughOfManyTestUser::create();
107107
$relation = $user->latest_login();
108108
$relation->addEagerConstraints([$user]);
109-
$this->assertSame('select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" in (1) group by "intermediates"."user_id"', $relation->getOneOfManySubQuery()->toSql());
109+
$this->assertSame('select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id"', $relation->getOneOfManySubQuery()->toSql());
110110
}
111111

112112
public function testEagerLoadingAppliesConstraintsToQuery(): void
113113
{
114114
$user = HasOneThroughOfManyTestUser::create();
115115
$relation = $user->latest_login();
116116
$relation->addEagerConstraints([$user]);
117-
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
117+
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latest_login" on "latest_login"."id_aggregate" = "logins"."id" and "latest_login"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
118118
}
119119

120120
public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalScope(): void
@@ -126,7 +126,7 @@ public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalSco
126126
$user = HasOneThroughOfManyTestUser::create();
127127
$relation = $user->latest_login_without_global_scope();
128128
$relation->addEagerConstraints([$user]);
129-
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latestOfMany" on "latestOfMany"."id_aggregate" = "logins"."id" and "latestOfMany"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
129+
$this->assertSame('select "logins".* from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" inner join (select MAX("logins"."id") as "id_aggregate", "intermediates"."user_id" from "logins" inner join "intermediates" on "intermediates"."id" = "logins"."intermediate_id" where "intermediates"."user_id" = ? and "intermediates"."user_id" in (1) group by "intermediates"."user_id") as "latestOfMany" on "latestOfMany"."id_aggregate" = "logins"."id" and "latestOfMany"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
130130

131131
HasOneThroughOfManyTestLogin::addGlobalScope('test', function ($query) {
132132
});
@@ -140,7 +140,7 @@ public function testGlobalScopeIsNotAppliedWhenRelationIsDefinedWithoutGlobalSco
140140

141141
$user = HasOneThroughOfManyTestUser::create();
142142
$relation = $user->price_without_global_scope();
143-
$this->assertSame('select "prices".* from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."id") as "id_aggregate", min("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" where "published_at" < ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "published_at" < ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."id_aggregate" = "prices"."id" and "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
143+
$this->assertSame('select "prices".* from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."id") as "id_aggregate", min("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" inner join (select max("prices"."published_at") as "published_at_aggregate", "intermediates"."user_id" from "prices" inner join "intermediates" on "intermediates"."id" = "prices"."intermediate_id" where "published_at" < ? and "intermediates"."user_id" = ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "published_at" < ? group by "intermediates"."user_id") as "price_without_global_scope" on "price_without_global_scope"."id_aggregate" = "prices"."id" and "price_without_global_scope"."published_at_aggregate" = "prices"."published_at" and "price_without_global_scope"."user_id" = "intermediates"."user_id" where "intermediates"."user_id" = ?', $relation->getQuery()->toSql());
144144

145145
HasOneThroughOfManyTestPrice::addGlobalScope('test', function ($query) {
146146
});

tests/Database/DatabaseEloquentMorphOneOfManyTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function testEagerLoadingAppliesConstraintsToInnerJoinSubQuery()
5858
$product = MorphOneOfManyTestProduct::create();
5959
$relation = $product->current_state();
6060
$relation->addEagerConstraints([$product]);
61-
$this->assertSame('select MAX("states"."id") as "id_aggregate", "states"."stateful_id", "states"."stateful_type" from "states" where "states"."stateful_id" in (1) and "states"."stateful_type" = ? group by "states"."stateful_id", "states"."stateful_type"', $relation->getOneOfManySubQuery()->toSql());
61+
$this->assertSame('select MAX("states"."id") as "id_aggregate", "states"."stateful_id", "states"."stateful_type" from "states" where "states"."stateful_type" = ? and "states"."stateful_id" = ? and "states"."stateful_id" is not null and "states"."stateful_id" in (1) and "states"."stateful_type" = ? group by "states"."stateful_id", "states"."stateful_type"', $relation->getOneOfManySubQuery()->toSql());
6262
}
6363

6464
public function testReceivingModel()

0 commit comments

Comments
 (0)