-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Franzose\DoctrineBulkInsert; | ||
|
||
use Doctrine\DBAL\Connection; | ||
use Doctrine\DBAL\Schema\Identifier; | ||
|
||
final class Query | ||
{ | ||
private $connection; | ||
|
||
public function __construct(Connection $connection) | ||
{ | ||
$this->connection = $connection; | ||
} | ||
|
||
public function execute(string $table, array $dataset, array $types = []): int | ||
{ | ||
if (empty($dataset)) { | ||
return 0; | ||
} | ||
|
||
$sql = sql($this->connection->getDatabasePlatform(), new Identifier($table), $dataset); | ||
|
||
return $this->connection->executeUpdate($sql, parameters($dataset), types($types, count($dataset))); | ||
} | ||
|
||
public function transactional(string $table, array $dataset, array $types = []): int | ||
{ | ||
return $this->connection->transactional(static function () use ($table, $dataset, $types): int { | ||
return $this->execute($table, $dataset, $types); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Franzose\DoctrineBulkInsert; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Schema\Identifier; | ||
|
||
function sql(AbstractPlatform $platform, Identifier $table, array $dataset): string | ||
{ | ||
$columns = quote_columns($platform, extract_columns($dataset)); | ||
|
||
$sql = sprintf( | ||
'INSERT INTO %s %s VALUES %s;', | ||
$table->getQuotedName($platform), | ||
stringify_columns($columns), | ||
generate_placeholders(count($columns), count($dataset)) | ||
); | ||
|
||
return $sql; | ||
} | ||
|
||
function extract_columns(array $dataset): array | ||
{ | ||
if (empty($dataset)) { | ||
return []; | ||
} | ||
|
||
$first = reset($dataset); | ||
|
||
return array_keys($first); | ||
} | ||
|
||
function quote_columns(AbstractPlatform $platform, array $columns): array | ||
{ | ||
return array_map(static function (string $column) use ($platform): string { | ||
return (new Identifier($column))->getQuotedName($platform); | ||
}, $columns); | ||
} | ||
|
||
function stringify_columns(array $columns): string | ||
{ | ||
return empty($columns) ? '' : sprintf('(%s)', implode(', ', $columns)); | ||
} | ||
|
||
function generate_placeholders(int $columnsLength, int $datasetLength): string | ||
{ | ||
// (?, ?, ?, ?) | ||
$placeholders = sprintf('(%s)', implode(', ', array_fill(0, $columnsLength, '?'))); | ||
|
||
// (?, ?), (?, ?) | ||
return implode(', ', array_fill(0, $datasetLength, $placeholders)); | ||
} | ||
|
||
function parameters(array $dataset): array | ||
{ | ||
return array_reduce($dataset, static function (array $flattenedValues, array $dataset): array { | ||
return array_merge($flattenedValues, array_values($dataset)); | ||
}, []); | ||
} | ||
|
||
function types(array $types, int $datasetLength): array | ||
{ | ||
if (empty($types)) { | ||
return []; | ||
} | ||
|
||
$types = array_values($types); | ||
|
||
$positionalTypes = []; | ||
|
||
for ($idx = 1; $idx <= $datasetLength; $idx++) { | ||
$positionalTypes = array_merge($positionalTypes, $types); | ||
} | ||
|
||
return $positionalTypes; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Franzose\DoctrineBulkInsert\Tests; | ||
|
||
use Doctrine\DBAL\Platforms\PostgreSqlPlatform; | ||
use Doctrine\DBAL\Schema\Identifier; | ||
use function Franzose\DoctrineBulkInsert\extract_columns; | ||
use function Franzose\DoctrineBulkInsert\parameters; | ||
use function Franzose\DoctrineBulkInsert\generate_placeholders; | ||
use function Franzose\DoctrineBulkInsert\sql; | ||
use function Franzose\DoctrineBulkInsert\stringify_columns; | ||
use function Franzose\DoctrineBulkInsert\types; | ||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
final class FunctionsTest extends TestCase | ||
{ | ||
use MockeryPHPUnitIntegration; | ||
|
||
public const DATASET = [ | ||
[ | ||
'foo' => 111, | ||
'bar' => 222, | ||
'qux' => 333 | ||
], | ||
[ | ||
'foo' => 444, | ||
'bar' => 555, | ||
'qux' => 777 | ||
], | ||
]; | ||
|
||
public function testExtractColumns(): void | ||
{ | ||
static::assertEquals(['foo', 'bar', 'qux'], extract_columns(static::DATASET)); | ||
static::assertEquals([], extract_columns([])); | ||
} | ||
|
||
public function testStringifyColumns(): void | ||
{ | ||
static::assertEquals('(foo, bar, qux)', stringify_columns(['foo', 'bar', 'qux'])); | ||
static::assertEquals('', stringify_columns([])); | ||
} | ||
|
||
public function testPlaceholders(): void | ||
{ | ||
static::assertEquals( | ||
'(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)', | ||
generate_placeholders(5, 2) | ||
); | ||
} | ||
|
||
public function testParameters(): void | ||
{ | ||
static::assertEquals([111, 222, 333, 444, 555, 777], parameters(static::DATASET)); | ||
static::assertEquals([], parameters([])); | ||
} | ||
|
||
public function testTypes(): void | ||
{ | ||
$types = ['string', 'text', 'json']; | ||
|
||
$expected = [ | ||
'string', 'text', 'json', | ||
'string', 'text', 'json' | ||
]; | ||
|
||
static::assertEquals($expected, types($types, 2)); | ||
} | ||
|
||
public function testSql(): void | ||
{ | ||
$sql = sql(new PostgreSqlPlatform(), new Identifier('foo'), static::DATASET); | ||
$expected = 'INSERT INTO foo (foo, bar, qux) VALUES (?, ?, ?), (?, ?, ?);'; | ||
|
||
static::assertEquals($expected, $sql); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Franzose\DoctrineBulkInsert\Tests; | ||
|
||
use Doctrine\DBAL\Connection; | ||
use Doctrine\DBAL\Platforms\PostgreSqlPlatform; | ||
use Franzose\DoctrineBulkInsert\Query; | ||
use Mockery; | ||
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
final class QueryTest extends TestCase | ||
{ | ||
use MockeryPHPUnitIntegration; | ||
|
||
public function testExecute(): void | ||
{ | ||
$connection = Mockery::mock(Connection::class); | ||
$connection->shouldReceive('getDatabasePlatform') | ||
->once() | ||
->andReturn(new PostgreSqlPlatform()); | ||
|
||
$connection->shouldReceive('executeUpdate') | ||
->once() | ||
->with('INSERT INTO foo (foo, bar) VALUES (?, ?), (?, ?);', [111, 222, 333, 444], []) | ||
->andReturn(2); | ||
|
||
$rows = (new Query($connection))->execute('foo', [ | ||
['foo' => 111, 'bar' => 222], | ||
['foo' => 333, 'bar' => 444], | ||
]); | ||
|
||
static::assertEquals(2, $rows); | ||
} | ||
|
||
public function testExecuteWithEmptyDataset(): void | ||
{ | ||
$connection = Mockery::mock(Connection::class); | ||
$connection->shouldNotReceive('getDatabasePlatform', 'executeUpdate'); | ||
|
||
$rows = (new Query($connection))->execute('foo', []); | ||
|
||
static::assertEquals(0, $rows); | ||
} | ||
} |