Skip to content

Commit

Permalink
Allow passing query Expression as column in Many-to-Many relationships (
Browse files Browse the repository at this point in the history
  • Loading branch information
plumthedev committed Mar 30, 2024
1 parent 978ee88 commit 150418a
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 29 deletions.
38 changes: 21 additions & 17 deletions src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
Expand Up @@ -65,7 +65,7 @@ class BelongsToMany extends Relation
/**
* The pivot table columns to retrieve.
*
* @var array
* @var array<string|\Illuminate\Contracts\Database\Query\Expression>
*/
protected $pivotColumns = [];

Expand Down Expand Up @@ -356,7 +356,7 @@ public function as($accessor)
/**
* Set a where clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @param string $boolean
Expand All @@ -372,7 +372,7 @@ public function wherePivot($column, $operator = null, $value = null, $boolean =
/**
* Set a "where between" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param array $values
* @param string $boolean
* @param bool $not
Expand All @@ -386,7 +386,7 @@ public function wherePivotBetween($column, array $values, $boolean = 'and', $not
/**
* Set a "or where between" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param array $values
* @return $this
*/
Expand All @@ -398,7 +398,7 @@ public function orWherePivotBetween($column, array $values)
/**
* Set a "where pivot not between" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param array $values
* @param string $boolean
* @return $this
Expand All @@ -411,7 +411,7 @@ public function wherePivotNotBetween($column, array $values, $boolean = 'and')
/**
* Set a "or where not between" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param array $values
* @return $this
*/
Expand All @@ -423,7 +423,7 @@ public function orWherePivotNotBetween($column, array $values)
/**
* Set a "where in" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $values
* @param string $boolean
* @param bool $not
Expand All @@ -439,7 +439,7 @@ public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
/**
* Set an "or where" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $operator
* @param mixed $value
* @return $this
Expand All @@ -454,7 +454,7 @@ public function orWherePivot($column, $operator = null, $value = null)
*
* In addition, new pivot records will receive this value.
*
* @param string|array $column
* @param string|\Illuminate\Contracts\Database\Query\Expression|array<string, string> $column
* @param mixed $value
* @return $this
*
Expand Down Expand Up @@ -494,7 +494,7 @@ public function orWherePivotIn($column, $values)
/**
* Set a "where not in" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param mixed $values
* @param string $boolean
* @return $this
Expand All @@ -519,7 +519,7 @@ public function orWherePivotNotIn($column, $values)
/**
* Set a "where null" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param string $boolean
* @param bool $not
* @return $this
Expand All @@ -534,7 +534,7 @@ public function wherePivotNull($column, $boolean = 'and', $not = false)
/**
* Set a "where not null" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param string $boolean
* @return $this
*/
Expand All @@ -546,7 +546,7 @@ public function wherePivotNotNull($column, $boolean = 'and')
/**
* Set a "or where null" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param bool $not
* @return $this
*/
Expand All @@ -558,7 +558,7 @@ public function orWherePivotNull($column, $not = false)
/**
* Set a "or where not null" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @return $this
*/
public function orWherePivotNotNull($column)
Expand All @@ -569,7 +569,7 @@ public function orWherePivotNotNull($column)
/**
* Add an "order by" clause for a pivot table column.
*
* @param string $column
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @param string $direction
* @return $this
*/
Expand Down Expand Up @@ -1558,11 +1558,15 @@ public function getPivotColumns()
/**
* Qualify the given column name by the pivot table.
*
* @param string $column
* @return string
* @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @return string|\Illuminate\Contracts\Database\Query\Expression
*/
public function qualifyPivotColumn($column)
{
if ($this->getGrammar()->isExpression($column)) {
return $column;
}

return str_contains($column, '.')
? $column
: $this->table.'.'.$column;
Expand Down
152 changes: 152 additions & 0 deletions tests/Database/DatabaseEloquentBelongsToManyExpressionTest.php
@@ -0,0 +1,152 @@
<?php

namespace Illuminate\Tests\Database;

use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use PHPUnit\Framework\TestCase;

class DatabaseEloquentBelongsToManyExpressionTest extends TestCase
{
protected function setUp(): void
{
$db = new DB;

$db->addConnection([
'driver' => 'sqlite',
'database' => ':memory:',
]);

$db->bootEloquent();
$db->setAsGlobal();

$this->createSchema();
}

public function testAmbiguousColumnsExpression(): void
{
$this->seedData();

$tags = DatabaseEloquentBelongsToManyExpressionTestTestPost::findOrFail(1)
->tags()
->wherePivotNotIn(new Expression("tag_id || '_' || type"), ['1_t1'])
->get();

$this->assertCount(1, $tags);
$this->assertEquals(2, $tags->first()->getKey());
}

public function testQualifiedColumnExpression(): void
{
$this->seedData();

$tags = DatabaseEloquentBelongsToManyExpressionTestTestPost::findOrFail(2)
->tags()
->wherePivotNotIn(new Expression("taggables.tag_id || '_' || taggables.type"), ['2_t2'])
->get();

$this->assertCount(1, $tags);
$this->assertEquals(3, $tags->first()->getKey());
}

/**
* Setup the database schema.
*
* @return void
*/
public function createSchema()
{
$this->schema()->create('posts', fn (Blueprint $t) => $t->id());
$this->schema()->create('tags', fn (Blueprint $t) => $t->id());
$this->schema()->create('taggables', function (Blueprint $t) {
$t->unsignedBigInteger('tag_id');
$t->unsignedBigInteger('taggable_id');
$t->string('type', 10);
$t->string('taggable_type');
}
);
}

/**
* Tear down the database schema.
*
* @return void
*/
protected function tearDown(): void
{
$this->schema()->drop('posts');
$this->schema()->drop('tags');
$this->schema()->drop('taggables');
}

/**
* Helpers...
*/
protected function seedData(): void
{
$p1 = DatabaseEloquentBelongsToManyExpressionTestTestPost::query()->create();
$p2 = DatabaseEloquentBelongsToManyExpressionTestTestPost::query()->create();
$t1 = DatabaseEloquentBelongsToManyExpressionTestTestTag::query()->create();
$t2 = DatabaseEloquentBelongsToManyExpressionTestTestTag::query()->create();
$t3 = DatabaseEloquentBelongsToManyExpressionTestTestTag::query()->create();

$p1->tags()->sync([
$t1->getKey() => ['type' => 't1'],
$t2->getKey() => ['type' => 't2'],
]);
$p2->tags()->sync([
$t2->getKey() => ['type' => 't2'],
$t3->getKey() => ['type' => 't3'],
]);
}

/**
* Get a database connection instance.
*
* @return \Illuminate\Database\ConnectionInterface
*/
protected function connection()
{
return Eloquent::getConnectionResolver()->connection();
}

/**
* Get a schema builder instance.
*
* @return \Illuminate\Database\Schema\Builder
*/
protected function schema()
{
return $this->connection()->getSchemaBuilder();
}
}

class DatabaseEloquentBelongsToManyExpressionTestTestPost extends Eloquent
{
protected $table = 'posts';
protected $fillable = ['id'];
public $timestamps = false;

public function tags(): MorphToMany
{
return $this->morphToMany(
DatabaseEloquentBelongsToManyExpressionTestTestTag::class,
'taggable',
'taggables',
'taggable_id',
'tag_id',
'id',
'id',
);
}
}

class DatabaseEloquentBelongsToManyExpressionTestTestTag extends Eloquent
{
protected $table = 'tags';
protected $fillable = ['id'];
public $timestamps = false;
}

0 comments on commit 150418a

Please sign in to comment.