Commit 818d7f66 authored by Eric Tucker's avatar Eric Tucker

`with` tests for hybrid relations and dont constrain mysql->mysql relations but…

`with` tests for hybrid relations and dont constrain mysql->mysql relations but we do need to constrain the mongo ones
parent 266305ca
...@@ -5,8 +5,7 @@ namespace Jenssegers\Mongodb\Helpers; ...@@ -5,8 +5,7 @@ namespace Jenssegers\Mongodb\Helpers;
use Closure; use Closure;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany; use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\Relation; use Jenssegers\Mongodb\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
trait QueriesRelationships trait QueriesRelationships
{ {
...@@ -28,9 +27,9 @@ trait QueriesRelationships ...@@ -28,9 +27,9 @@ trait QueriesRelationships
$relation = $this->getRelationWithoutConstraints($relation); $relation = $this->getRelationWithoutConstraints($relation);
// If this is a hybrid relation then we can not use an existence query // If this is a hybrid relation then we can not use a normal whereExists() query that relies on a subquery
// We need to use a `whereIn` query // We need to use a `whereIn` query
if ($relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName()) { if ($this->getModel() instanceof Model || $this->isAcrossConnections($relation)) {
return $this->addHybridHas($relation, $operator, $count, $boolean, $callback); return $this->addHybridHas($relation, $operator, $count, $boolean, $callback);
} }
...@@ -57,6 +56,15 @@ trait QueriesRelationships ...@@ -57,6 +56,15 @@ trait QueriesRelationships
); );
} }
/**
* @param $relation
* @return bool
*/
protected function isAcrossConnections($relation)
{
return $relation->getParent()->getConnectionName() !== $relation->getRelated()->getConnectionName();
}
/** /**
* Compare across databases * Compare across databases
* @param $relation * @param $relation
...@@ -65,6 +73,7 @@ trait QueriesRelationships ...@@ -65,6 +73,7 @@ trait QueriesRelationships
* @param string $boolean * @param string $boolean
* @param Closure|null $callback * @param Closure|null $callback
* @return mixed * @return mixed
* @throws \Exception
*/ */
public function addHybridHas($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) public function addHybridHas($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{ {
...@@ -73,15 +82,23 @@ trait QueriesRelationships ...@@ -73,15 +82,23 @@ trait QueriesRelationships
$hasQuery->callScope($callback); $hasQuery->callScope($callback);
} }
// If the operator is <, <= or !=, we will use whereNotIn.
$not = in_array($operator, ['<', '<=', '!=']);
// If we are comparing to 0, we need an additional $not flip.
if ($count == 0) {
$not = ! $not;
}
$relations = $hasQuery->pluck($this->getHasCompareKey($relation)); $relations = $hasQuery->pluck($this->getHasCompareKey($relation));
$constraintKey = $this->getRelatedConstraintKey($relation);
return $this->addRelatedCountConstraint($constraintKey, $relations, $operator, $count, $boolean); $relatedIds = $this->getConstrainedRelatedIds($relations, $operator, $count);
return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not);
} }
/** /**
* Returns key we are constraining this parent model's query witth * Returns key we are constraining this parent model's query with
* @param $relation * @param $relation
* @return string * @return string
* @throws \Exception * @throws \Exception
...@@ -89,7 +106,7 @@ trait QueriesRelationships ...@@ -89,7 +106,7 @@ trait QueriesRelationships
protected function getRelatedConstraintKey($relation) protected function getRelatedConstraintKey($relation)
{ {
if ($relation instanceof HasOneOrMany) { if ($relation instanceof HasOneOrMany) {
return $relation->getQualifiedParentKeyName(); return $this->model->getKeyName();
} }
if ($relation instanceof BelongsTo) { if ($relation instanceof BelongsTo) {
...@@ -105,47 +122,20 @@ trait QueriesRelationships ...@@ -105,47 +122,20 @@ trait QueriesRelationships
*/ */
protected function getHasCompareKey($relation) protected function getHasCompareKey($relation)
{ {
if ($relation instanceof HasOneOrMany) { if (method_exists($relation, 'getHasCompareKey')) {
return $relation->getForeignKeyName(); return $relation->getHasCompareKey();
} }
$keyMethods = ['getOwnerKey', 'getHasCompareKey']; return $relation instanceof HasOneOrMany ? $relation->getForeignKeyName() : $relation->getOwnerKey();
foreach ($keyMethods as $method) {
if (method_exists($relation, $method)) {
return $relation->$method();
}
}
} }
/** /**
* Add the "has" condition where clause to the query.
*
* @param \Illuminate\Database\Eloquent\Builder $hasQuery
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Builder|static
*/
protected function addHasWhere(EloquentBuilder $hasQuery, Relation $relation, $operator, $count, $boolean)
{
$query = $hasQuery->getQuery();
// Get the number of related objects for each possible parent.
$relations = $query->pluck($relation->getHasCompareKey());
return $this->addRelatedCountConstraint($this->model->getKeyName(), $relations, $operator, $count, $boolean);
}
/**
* Consta
* @param $key
* @param $relations * @param $relations
* @param $operator * @param $operator
* @param $count * @param $count
* @param $boolean * @return array
* @return mixed
*/ */
protected function addRelatedCountConstraint($key, $relations, $operator, $count, $boolean) protected function getConstrainedRelatedIds($relations, $operator, $count)
{ {
$relationCount = array_count_values(array_map(function ($id) { $relationCount = array_count_values(array_map(function ($id) {
return (string)$id; // Convert Back ObjectIds to Strings return (string)$id; // Convert Back ObjectIds to Strings
...@@ -169,16 +159,7 @@ trait QueriesRelationships ...@@ -169,16 +159,7 @@ trait QueriesRelationships
} }
}); });
// If the operator is <, <= or !=, we will use whereNotIn.
$not = in_array($operator, ['<', '<=', '!=']);
// If we are comparing to 0, we need an additional $not flip.
if ($count == 0) {
$not = ! $not;
}
// All related ids. // All related ids.
$relatedIds = array_keys($relationCount); return array_keys($relationCount);
// Add whereIn to the query.
return $this->whereIn($key, $relatedIds, $boolean, $not);
} }
} }
\ No newline at end of file
...@@ -76,7 +76,7 @@ class HybridRelationsTest extends TestCase ...@@ -76,7 +76,7 @@ class HybridRelationsTest extends TestCase
} }
public function testRelationConstraints() public function testHybridWhereHas()
{ {
$user = new MysqlUser; $user = new MysqlUser;
$otherUser = new MysqlUser; $otherUser = new MysqlUser;
...@@ -129,4 +129,66 @@ class HybridRelationsTest extends TestCase ...@@ -129,4 +129,66 @@ class HybridRelationsTest extends TestCase
$this->assertEquals(2, $books->count()); $this->assertEquals(2, $books->count());
} }
public function testHybridWith()
{
$user = new MysqlUser;
$otherUser = new MysqlUser;
$this->assertInstanceOf('MysqlUser', $user);
$this->assertInstanceOf('Illuminate\Database\MySqlConnection', $user->getConnection());
$this->assertInstanceOf('MysqlUser', $otherUser);
$this->assertInstanceOf('Illuminate\Database\MySqlConnection', $otherUser->getConnection());
//MySql User
$user->name = "John Doe";
$user->id = 2;
$user->save();
// Other user
$otherUser->name = 'Other User';
$otherUser->id = 3;
$otherUser->save();
// Make sure they are created
$this->assertTrue(is_int($user->id));
$this->assertTrue(is_int($otherUser->id));
// Clear to start
Book::truncate();
MysqlBook::truncate();
// Create books
// Mysql relation
$user->mysqlBooks()->saveMany([
new MysqlBook(['title' => 'Game of Thrones']),
new MysqlBook(['title' => 'Harry Potter']),
]);
$otherUser->mysqlBooks()->saveMany([
new MysqlBook(['title' => 'Harry Plants']),
new MysqlBook(['title' => 'Harveys']),
new MysqlBook(['title' => 'Harry Planter']),
]);
// SQL has many Hybrid
$user->books()->saveMany([
new Book(['title' => 'Game of Thrones']),
new Book(['title' => 'Harry Potter']),
]);
$otherUser->books()->saveMany([
new Book(['title' => 'Harry Plants']),
new Book(['title' => 'Harveys']),
new Book(['title' => 'Harry Planter']),
]);
MysqlUser::with('books')->get()
->each(function ($user) {
$this->assertEquals($user->id, $user->books->count());
});
MysqlUser::whereHas('mysqlBooks', function ($query) {
return $query->where('title', 'LIKE', 'Harry%');
})
->with('books')
->get()
->each(function ($user) {
$this->assertEquals($user->id, $user->books->count());
});
}
} }
...@@ -27,7 +27,8 @@ class MysqlBook extends Eloquent ...@@ -27,7 +27,8 @@ class MysqlBook extends Eloquent
if (!$schema->hasTable('books')) { if (!$schema->hasTable('books')) {
Schema::connection('mysql')->create('books', function ($table) { Schema::connection('mysql')->create('books', function ($table) {
$table->string('title'); $table->string('title');
$table->string('author_id'); $table->string('author_id')->nullable();
$table->integer('mysql_user_id')->unsigned()->nullable();
$table->timestamps(); $table->timestamps();
}); });
} }
......
...@@ -21,6 +21,11 @@ class MysqlUser extends Eloquent ...@@ -21,6 +21,11 @@ class MysqlUser extends Eloquent
return $this->hasOne('Role'); return $this->hasOne('Role');
} }
public function mysqlBooks()
{
return $this->hasMany(MysqlBook::class);
}
/** /**
* Check if we need to run the schema. * Check if we need to run the schema.
*/ */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment