diff --git a/src/Database/Query/Builder.php b/src/Database/Query/Builder.php index 96c135e..66a7fb2 100755 --- a/src/Database/Query/Builder.php +++ b/src/Database/Query/Builder.php @@ -2,7 +2,10 @@ namespace Awobaz\Compoships\Database\Query; +use Illuminate\Database\MySqlConnection; +use Illuminate\Database\PostgresConnection; use Illuminate\Database\Query\Builder as BaseQueryBuilder; +use Illuminate\Database\SQLiteConnection; use Illuminate\Support\Arr; class Builder extends BaseQueryBuilder @@ -21,17 +24,39 @@ public function whereIn($column, $values, $boolean = 'and', $not = false) { // Here we implement custom support for multi-column 'IN' if (is_array($column)) { - $inOperator = $not ? 'NOT IN' : 'IN'; - $prefix = $this->getConnection()->getTablePrefix(); - - foreach ($column as &$value) { - $value = $prefix.$value; + $connection = $this->getConnection(); + // Check if we can use optimized row value expressions + if ( + ($connection instanceof MySqlConnection || + $connection instanceof PostgresConnection || + $connection instanceof SQLiteConnection) && + Arr::every($values, fn ($value) => !in_array(null, $value, true)) + ) { + $inOperator = $not ? 'NOT IN' : 'IN'; + $prefix = $connection->getTablePrefix(); + + foreach ($column as &$value) { + $value = $prefix.$value; + } + + $columns = implode(',', $column); + $tuplePlaceholders = '('.implode(',', array_fill(0, count($column), '?')).')'; + $placeholderList = implode(',', array_fill(0, count($values), $tuplePlaceholders)); + $this->whereRaw("({$columns}) {$inOperator} (VALUES {$placeholderList})", Arr::flatten($values), $boolean); + + return $this; } - $columns = implode(',', $column); - $tuplePlaceholders = '('.implode(', ', array_fill(0, count($column), '?')).')'; - $placeholderList = implode(',', array_fill(0, count($values), $tuplePlaceholders)); - $this->whereRaw("({$columns}) {$inOperator} ({$placeholderList})", Arr::flatten($values), $boolean); + // Otherwise use a series of OR/AND clauses + $this->where(function ($query) use ($column, $values) { + foreach ($values as $value) { + $query->orWhere(function ($query) use ($column, $value) { + foreach ($column as $index => $aColumn) { + $query->where($aColumn, $value[$index]); + } + }); + } + }); return $this; } diff --git a/tests/Unit/BuilderTest.php b/tests/Unit/BuilderTest.php index 73edecd..28f1563 100644 --- a/tests/Unit/BuilderTest.php +++ b/tests/Unit/BuilderTest.php @@ -20,6 +20,7 @@ class BuilderTest extends TestCase * @covers \Awobaz\Compoships\Database\Eloquent\Concerns\HasRelationships::sanitizeKey * @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany::addConstraints * @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany::getQualifiedParentKeyName + * @covers \Awobaz\Compoships\Database\Query\Builder::whereIn * @covers \Awobaz\Compoships\Database\Query\Builder::whereColumn */ public function test_Illuminate_hasOneOrMany__Builder_whereColumn_on_relation_column() @@ -58,4 +59,29 @@ public function test_Illuminate_hasOneOrMany__Builder_whereColumn_on_relation_co $this->assertCount(1, $allocations); $this->assertCount(2, $allocations[0]->originalPackages); } + + public function test_use_row_value_expressions_for_eager_loading_when_possible() + { + $allocationId1 = Capsule::table('allocations')->insertGetId([ + 'user_id' => 1, + 'booking_id' => 1, + ]); + $allocationId2 = Capsule::table('allocations')->insertGetId([ + 'user_id' => 2, + 'booking_id' => null, + ]); + $allocation1 = Allocation::find($allocationId1); + $allocation2 = Allocation::find($allocationId2); + + $query1 = Allocation::query()->getRelation('user'); + $query1->addEagerConstraints([$allocation1]); + $sql1 = $query1->toRawSql(); + + $query2 = Allocation::query()->getRelation('user'); + $query2->addEagerConstraints([$allocation2]); + $sql2 = $query2->toRawSql(); + + $this->assertEquals('select * from "users" where (users.id,users.booking_id) IN (VALUES (1,1))', $sql1); + $this->assertEquals('select * from "users" where (("users"."id" = 2 and "users"."booking_id" is null))', $sql2); + } }