Skip to content

Commit 818d7f6

Browse files
committed
with tests for hybrid relations and dont constrain mysql->mysql relations but we do need to constrain the mongo ones
1 parent 266305c commit 818d7f6

File tree

4 files changed

+101
-52
lines changed

4 files changed

+101
-52
lines changed

src/Jenssegers/Mongodb/Helpers/QueriesRelationships.php

Lines changed: 31 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
use Closure;
66
use Illuminate\Database\Eloquent\Relations\BelongsTo;
77
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
8-
use Illuminate\Database\Eloquent\Relations\Relation;
9-
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
8+
use Jenssegers\Mongodb\Eloquent\Model;
109

1110
trait QueriesRelationships
1211
{
@@ -28,9 +27,9 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C
2827

2928
$relation = $this->getRelationWithoutConstraints($relation);
3029

31-
// If this is a hybrid relation then we can not use an existence query
30+
// If this is a hybrid relation then we can not use a normal whereExists() query that relies on a subquery
3231
// We need to use a `whereIn` query
33-
if ($relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName()) {
32+
if ($this->getModel() instanceof Model || $this->isAcrossConnections($relation)) {
3433
return $this->addHybridHas($relation, $operator, $count, $boolean, $callback);
3534
}
3635

@@ -57,6 +56,15 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C
5756
);
5857
}
5958

59+
/**
60+
* @param $relation
61+
* @return bool
62+
*/
63+
protected function isAcrossConnections($relation)
64+
{
65+
return $relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName();
66+
}
67+
6068
/**
6169
* Compare across databases
6270
* @param $relation
@@ -65,6 +73,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C
6573
* @param string $boolean
6674
* @param Closure|null $callback
6775
* @return mixed
76+
* @throws \Exception
6877
*/
6978
public function addHybridHas($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
7079
{
@@ -73,23 +82,31 @@ public function addHybridHas($relation, $operator = '>=', $count = 1, $boolean =
7382
$hasQuery->callScope($callback);
7483
}
7584

85+
// If the operator is <, <= or !=, we will use whereNotIn.
86+
$not = in_array($operator, ['<', '<=', '!=']);
87+
// If we are comparing to 0, we need an additional $not flip.
88+
if ($count == 0) {
89+
$not = ! $not;
90+
}
91+
7692
$relations = $hasQuery->pluck($this->getHasCompareKey($relation));
77-
$constraintKey = $this->getRelatedConstraintKey($relation);
7893

79-
return $this->addRelatedCountConstraint($constraintKey, $relations, $operator, $count, $boolean);
94+
$relatedIds = $this->getConstrainedRelatedIds($relations, $operator, $count);
95+
96+
return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not);
8097
}
8198

8299

83100
/**
84-
* Returns key we are constraining this parent model's query witth
101+
* Returns key we are constraining this parent model's query with
85102
* @param $relation
86103
* @return string
87104
* @throws \Exception
88105
*/
89106
protected function getRelatedConstraintKey($relation)
90107
{
91108
if ($relation instanceof HasOneOrMany) {
92-
return $relation->getQualifiedParentKeyName();
109+
return $this->model->getKeyName();
93110
}
94111

95112
if ($relation instanceof BelongsTo) {
@@ -105,47 +122,20 @@ protected function getRelatedConstraintKey($relation)
105122
*/
106123
protected function getHasCompareKey($relation)
107124
{
108-
if ($relation instanceof HasOneOrMany) {
109-
return $relation->getForeignKeyName();
125+
if (method_exists($relation, 'getHasCompareKey')) {
126+
return $relation->getHasCompareKey();
110127
}
111128

112-
$keyMethods = ['getOwnerKey', 'getHasCompareKey'];
113-
foreach ($keyMethods as $method) {
114-
if (method_exists($relation, $method)) {
115-
return $relation->$method();
116-
}
117-
}
129+
return $relation instanceof HasOneOrMany ? $relation->getForeignKeyName() : $relation->getOwnerKey();
118130
}
119131

120132
/**
121-
* Add the "has" condition where clause to the query.
122-
*
123-
* @param \Illuminate\Database\Eloquent\Builder $hasQuery
124-
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
125-
* @param string $operator
126-
* @param int $count
127-
* @param string $boolean
128-
* @return \Illuminate\Database\Eloquent\Builder|static
129-
*/
130-
protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $operator, $count, $boolean)
131-
{
132-
$query = $hasQuery->getQuery();
133-
// Get the number of related objects for each possible parent.
134-
$relations = $query->pluck($relation->getHasCompareKey());
135-
136-
return $this->addRelatedCountConstraint($this->model->getKeyName(), $relations, $operator, $count, $boolean);
137-
}
138-
139-
/**
140-
* Consta
141-
* @param $key
142133
* @param $relations
143134
* @param $operator
144135
* @param $count
145-
* @param $boolean
146-
* @return mixed
136+
* @return array
147137
*/
148-
protected function addRelatedCountConstraint($key, $relations, $operator, $count, $boolean)
138+
protected function getConstrainedRelatedIds($relations, $operator, $count)
149139
{
150140
$relationCount = array_count_values(array_map(function ($id) {
151141
return (string)$id; // Convert Back ObjectIds to Strings
@@ -169,16 +159,7 @@ protected function addRelatedCountConstraint($key, $relations, $operator, $count
169159
}
170160
});
171161

172-
// If the operator is <, <= or !=, we will use whereNotIn.
173-
$not = in_array($operator, ['<', '<=', '!=']);
174-
// If we are comparing to 0, we need an additional $not flip.
175-
if ($count == 0) {
176-
$not = ! $not;
177-
}
178162
// All related ids.
179-
$relatedIds = array_keys($relationCount);
180-
181-
// Add whereIn to the query.
182-
return $this->whereIn($key, $relatedIds, $boolean, $not);
163+
return array_keys($relationCount);
183164
}
184165
}

tests/HybridRelationsTest.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function testMysqlRelations()
7676
}
7777

7878

79-
public function testRelationConstraints()
79+
public function testHybridWhereHas()
8080
{
8181
$user = new MysqlUser;
8282
$otherUser = new MysqlUser;
@@ -129,4 +129,66 @@ public function testRelationConstraints()
129129

130130
$this->assertEquals(2, $books->count());
131131
}
132+
133+
public function testHybridWith()
134+
{
135+
$user = new MysqlUser;
136+
$otherUser = new MysqlUser;
137+
$this->assertInstanceOf('MysqlUser', $user);
138+
$this->assertInstanceOf('Illuminate\Database\MySqlConnection', $user->getConnection());
139+
$this->assertInstanceOf('MysqlUser', $otherUser);
140+
$this->assertInstanceOf('Illuminate\Database\MySqlConnection', $otherUser->getConnection());
141+
142+
//MySql User
143+
$user->name = "John Doe";
144+
$user->id = 2;
145+
$user->save();
146+
// Other user
147+
$otherUser->name = 'Other User';
148+
$otherUser->id = 3;
149+
$otherUser->save();
150+
// Make sure they are created
151+
$this->assertTrue(is_int($user->id));
152+
$this->assertTrue(is_int($otherUser->id));
153+
// Clear to start
154+
Book::truncate();
155+
MysqlBook::truncate();
156+
// Create books
157+
// Mysql relation
158+
$user->mysqlBooks()->saveMany([
159+
new MysqlBook(['title' => 'Game of Thrones']),
160+
new MysqlBook(['title' => 'Harry Potter']),
161+
]);
162+
163+
$otherUser->mysqlBooks()->saveMany([
164+
new MysqlBook(['title' => 'Harry Plants']),
165+
new MysqlBook(['title' => 'Harveys']),
166+
new MysqlBook(['title' => 'Harry Planter']),
167+
]);
168+
// SQL has many Hybrid
169+
$user->books()->saveMany([
170+
new Book(['title' => 'Game of Thrones']),
171+
new Book(['title' => 'Harry Potter']),
172+
]);
173+
174+
$otherUser->books()->saveMany([
175+
new Book(['title' => 'Harry Plants']),
176+
new Book(['title' => 'Harveys']),
177+
new Book(['title' => 'Harry Planter']),
178+
]);
179+
180+
MysqlUser::with('books')->get()
181+
->each(function ($user) {
182+
$this->assertEquals($user->id, $user->books->count());
183+
});
184+
185+
MysqlUser::whereHas('mysqlBooks', function ($query) {
186+
return $query->where('title', 'LIKE', 'Harry%');
187+
})
188+
->with('books')
189+
->get()
190+
->each(function ($user) {
191+
$this->assertEquals($user->id, $user->books->count());
192+
});
193+
}
132194
}

tests/models/MysqlBook.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public static function executeSchema()
2727
if (!$schema->hasTable('books')) {
2828
Schema::connection('mysql')->create('books', function ($table) {
2929
$table->string('title');
30-
$table->string('author_id');
30+
$table->string('author_id')->nullable();
31+
$table->integer('mysql_user_id')->unsigned()->nullable();
3132
$table->timestamps();
3233
});
3334
}

tests/models/MysqlUser.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ public function role()
2121
return $this->hasOne('Role');
2222
}
2323

24+
public function mysqlBooks()
25+
{
26+
return $this->hasMany(MysqlBook::class);
27+
}
28+
2429
/**
2530
* Check if we need to run the schema.
2631
*/

0 commit comments

Comments
 (0)