Skip to content

Commit

Permalink
Wrote the code
Browse files Browse the repository at this point in the history
  • Loading branch information
franzose committed Apr 7, 2019
1 parent fe6c0b9 commit 21eb962
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/Query.php
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);
});
}
}
77 changes: 77 additions & 0 deletions src/functions.php
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;
}
79 changes: 79 additions & 0 deletions tests/FunctionsTest.php
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);
}
}
46 changes: 46 additions & 0 deletions tests/QueryTest.php
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);
}
}

0 comments on commit 21eb962

Please sign in to comment.