Skip to content

Commit 92f613c

Browse files
authored
Support insertOrIgnoreUsing to \Hyperf\Database\Query\Builder (#6783)
1 parent 96ba44c commit 92f613c

File tree

6 files changed

+101
-3
lines changed

6 files changed

+101
-3
lines changed

src/Model/Builder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class Builder
9494
protected $passthru = [
9595
'insert', 'insertGetId', 'getBindings', 'toSql', 'toRawSql', 'insertOrIgnore',
9696
'exists', 'doesntExist', 'count', 'min', 'max', 'avg', 'average', 'sum', 'getConnection',
97-
'upsert', 'updateOrInsert',
97+
'upsert', 'updateOrInsert', 'insertOrIgnoreUsing',
9898
];
9999

100100
/**

src/Query/Builder.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,19 @@ public function insertUsing(array $columns, $query)
24412441
return $this->connection->insert($this->grammar->compileInsertUsing($this, $columns, $sql), $this->cleanBindings($bindings));
24422442
}
24432443

2444+
/**
2445+
* Insert new records into the table using a subquery while ignoring errors.
2446+
*/
2447+
public function insertOrIgnoreUsing(array $columns, array|Builder|Closure|ModelBuilder|string $query): int
2448+
{
2449+
[$sql, $bindings] = $this->createSub($query);
2450+
2451+
return $this->connection->affectingStatement(
2452+
$this->grammar->compileInsertOrIgnoreUsing($this, $columns, $sql),
2453+
$this->cleanBindings($bindings)
2454+
);
2455+
}
2456+
24442457
/**
24452458
* Insert ignore a new record into the database.
24462459
*/

src/Query/Grammars/Grammar.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,20 @@ public function compileInsertGetId(Builder $query, $values, $sequence): string
163163
*/
164164
public function compileInsertUsing(Builder $query, array $columns, string $sql): string
165165
{
166-
return "insert into {$this->wrapTable($query->from)} ({$this->columnize($columns)}) {$sql}";
166+
$table = $this->wrapTable($query->from);
167+
if (empty($columns) || $columns === ['*']) {
168+
return "insert into {$table} {$sql}";
169+
}
170+
171+
return "insert into {$table} ({$this->columnize($columns)}) {$sql}";
172+
}
173+
174+
/**
175+
* Compile an insert ignore statement using a subquery into SQL.
176+
*/
177+
public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql): string
178+
{
179+
throw new RuntimeException('This database engine does not support inserting while ignoring errors.');
167180
}
168181

169182
/**

src/Query/Grammars/MySqlGrammar.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Hyperf\Database\Query\IndexHint;
1818
use Hyperf\Database\Query\JoinLateralClause;
1919
use Hyperf\Database\Query\JsonExpression;
20+
use Hyperf\Stringable\Str;
2021

2122
use function Hyperf\Collection\collect;
2223

@@ -71,6 +72,14 @@ public function compileInsertOrIgnore(Builder $query, array $values): string
7172
return substr_replace($this->compileInsert($query, $values), ' ignore', 6, 0);
7273
}
7374

75+
/**
76+
* Compile an insert ignore statement using a subquery into SQL.
77+
*/
78+
public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql): string
79+
{
80+
return Str::replaceFirst('insert', 'insert ignore', $this->compileInsertUsing($query, $columns, $sql));
81+
}
82+
7483
/**
7584
* Compile the random statement into SQL.
7685
*

tests/DatabaseQueryBuilderTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace HyperfTest\Database;
1414

1515
use Hyperf\Context\ApplicationContext;
16+
use Hyperf\Database\Connection;
1617
use Hyperf\Database\ConnectionInterface;
1718
use Hyperf\Database\Exception\InvalidBindingException;
1819
use Hyperf\Database\Query\Builder;
@@ -21,11 +22,13 @@
2122
use Hyperf\Database\Query\Grammars\MySqlGrammar;
2223
use Hyperf\Database\Query\Processors\MySqlProcessor;
2324
use Hyperf\Database\Query\Processors\Processor;
25+
use InvalidArgumentException;
2426
use Mockery as m;
2527
use Mockery\MockInterface;
2628
use PHPUnit\Framework\Attributes\CoversNothing;
2729
use PHPUnit\Framework\TestCase;
2830
use Psr\Container\ContainerInterface;
31+
use RuntimeException;
2932
use TypeError;
3033

3134
/**
@@ -889,6 +892,63 @@ public function testWhereAny()
889892
$this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings());
890893
}
891894

895+
public function testInsertOrIgnoreUsingMethod(): void
896+
{
897+
$this->expectException(RuntimeException::class);
898+
$this->expectExceptionMessage('does not support');
899+
$builder = $this->getBuilder();
900+
$builder->from('users')->insertOrIgnoreUsing(['email' => 'foo'], 'bar');
901+
}
902+
903+
public function testMySqlInsertOrIgnoreUsingMethod(): void
904+
{
905+
$builder = $this->getMySqlBuilder();
906+
$builder->getConnection()->shouldReceive('getDatabaseName');
907+
$builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert ignore into `table1` (`foo`) select `bar` from `table2` where `foreign_id` = ?', [0 => 5])->andReturn(1);
908+
909+
$result = $builder->from('table1')->insertOrIgnoreUsing(
910+
['foo'],
911+
function (Builder $query) {
912+
$query->select(['bar'])->from('table2')->where('foreign_id', '=', 5);
913+
}
914+
);
915+
916+
$this->assertEquals(1, $result);
917+
}
918+
919+
public function testMySqlInsertOrIgnoreUsingWithEmptyColumns(): void
920+
{
921+
$builder = $this->getMySqlBuilder();
922+
/**
923+
* @var Connection&MockInterface $connection
924+
*/
925+
$connection = $builder->getConnection();
926+
$connection->allows('getDatabaseName');
927+
$connection->allows('affectingStatement')
928+
->once()
929+
->andReturnUsing(function ($sql, $bindings) {
930+
$this->assertSame('insert ignore into `table1` select * from `table2` where `foreign_id` = ?', $sql);
931+
$this->assertSame([0 => 5], $bindings);
932+
return 1;
933+
});
934+
935+
$result = $builder->from('table1')->insertOrIgnoreUsing(
936+
[],
937+
function (Builder $query) {
938+
$query->from('table2')->where('foreign_id', '=', 5);
939+
}
940+
);
941+
942+
$this->assertEquals(1, $result);
943+
}
944+
945+
public function testMySqlInsertOrIgnoreUsingInvalidSubquery(): void
946+
{
947+
$this->expectException(InvalidArgumentException::class);
948+
$builder = $this->getMySqlBuilder();
949+
$builder->from('table1')->insertOrIgnoreUsing(['foo'], ['bar']);
950+
}
951+
892952
public function testOrWhereAny()
893953
{
894954
$builder = $this->getBuilder();

tests/ModelBuilderTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,11 @@ public function testQueryPassThru()
600600
$this->assertInstanceOf(Builder::class, $builder->foobar());
601601

602602
$builder = $this->getBuilder();
603-
$builder->getQuery()->shouldReceive('insert')->once()->with(['bar'])->andReturn('foo');
603+
$builder->getQuery()->shouldReceive('insertOrIgnoreUsing')->once()->with(['bar'], 'baz')->andReturn(1);
604+
$this->assertSame(1, $builder->insertOrIgnoreUsing(['bar'], 'baz'));
604605

606+
$builder = $this->getBuilder();
607+
$builder->getQuery()->shouldReceive('insert')->once()->with(['bar'])->andReturn('foo');
605608
$this->assertEquals('foo', $builder->insert(['bar']));
606609
}
607610

0 commit comments

Comments
 (0)