-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Database): add ChunkWriteService
- Loading branch information
Showing
5 changed files
with
196 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,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaraStrict\Database\Contracts; | ||
|
||
use Closure; | ||
use Generator; | ||
use Illuminate\Database\Eloquent\Model; | ||
use LaraStrict\Database\Entities\ChunkWriteStateEntity; | ||
|
||
interface ChunkWriteServiceContract | ||
{ | ||
/** | ||
* @param Closure(): Generator<int, Model> $closure | ||
*/ | ||
public function write(Closure $closure): ChunkWriteStateEntity; | ||
} |
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,23 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaraStrict\Database\Entities; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
|
||
class ChunkWriteStateEntity | ||
{ | ||
/** | ||
* @param class-string<Model>|null $modelClass | ||
* @param array<array<string, string|int|bool|float>> $toWrite | ||
*/ | ||
public function __construct( | ||
public int $batchSize = 0, | ||
public ?string $modelClass = null, | ||
public int $insertedCount = 0, | ||
public int $attributesCount = 0, | ||
public array $toWrite = [], | ||
) { | ||
} | ||
} |
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,82 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaraStrict\Database\Services; | ||
|
||
use Closure; | ||
use Illuminate\Database\Eloquent\Model; | ||
use LaraStrict\Database\Contracts\ChunkWriteServiceContract; | ||
use LaraStrict\Database\Entities\ChunkWriteStateEntity; | ||
use LogicException; | ||
|
||
final class ChunkWriteService implements ChunkWriteServiceContract | ||
{ | ||
public function write(Closure $closure, int $batchSize = 0): ChunkWriteStateEntity | ||
{ | ||
$writeState = new ChunkWriteStateEntity(batchSize: $batchSize); | ||
|
||
foreach ($closure() as $model) { | ||
$this->add($model, $writeState); | ||
} | ||
|
||
$this->finish($writeState); | ||
|
||
return $writeState; | ||
} | ||
|
||
private function add(Model $model, ChunkWriteStateEntity $state): void | ||
{ | ||
if ($model->usesTimestamps()) { | ||
$model->updateTimestamps(); | ||
} | ||
|
||
/** @var array<string, string|int|bool|float> $attributes */ | ||
$attributes = $model->getAttributes(); | ||
$attributesCount = count($attributes); | ||
|
||
$modelClass = $model::class; | ||
|
||
if ($state->modelClass === null) { | ||
$state->modelClass = $modelClass; | ||
} elseif ($state->modelClass !== $modelClass) { | ||
throw new LogicException(sprintf( | ||
'Batch insert must contain items with same class <%s> got <%s>', | ||
$state->modelClass, | ||
$modelClass | ||
)); | ||
} | ||
|
||
// We need to prevent insert max statements by limiting number of insert | ||
if ($state->batchSize === 0) { | ||
$state->batchSize = (int) (65536 / $attributesCount); | ||
} | ||
|
||
if ($state->attributesCount !== 0 && $state->attributesCount !== $attributesCount) { | ||
throw new LogicException('Batch insert must contain items with same attributes count' . print_r( | ||
$attributes, | ||
true | ||
)); | ||
} | ||
|
||
$state->toWrite[] = $attributes; | ||
$state->attributesCount = $attributesCount; | ||
|
||
if ($state->batchSize === count($state->toWrite)) { | ||
$this->finish($state); | ||
} | ||
} | ||
|
||
private function finish(ChunkWriteStateEntity $state): void | ||
{ | ||
if ($state->toWrite === [] || $state->modelClass === null) { | ||
return; | ||
} | ||
|
||
// Do not fail on duplicated entries. | ||
$count = $state->modelClass::insertOrIgnore($state->toWrite); | ||
|
||
$state->insertedCount += $count; | ||
$state->toWrite = []; | ||
} | ||
} |
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,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\LaraStrict\Unit\Database\Services; | ||
|
||
use Closure; | ||
use LaraStrict\Database\Entities\ChunkWriteStateEntity; | ||
use LaraStrict\Database\Services\ChunkWriteService; | ||
use LaraStrict\Tests\Traits\SqlTestEnable; | ||
use PHPUnit\Framework\Assert; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
final class ChunkWriteServiceTest extends TestCase | ||
{ | ||
use SqlTestEnable; | ||
|
||
/** | ||
* @return array<string|int, array{0: Closure(static):void}> | ||
*/ | ||
public static function data(): array | ||
{ | ||
return [ | ||
[ | ||
'empty' => static function (self $self) { | ||
$self->assert(new ChunkWriteStateEntity(), static function () { | ||
yield from []; | ||
},); | ||
}, | ||
], | ||
[ | ||
static function (self $self) { | ||
$self->assert( | ||
new ChunkWriteStateEntity(32768, TestModel::class, 3, 2), | ||
static function () { | ||
yield from [new TestModel(), new TestModel(), new TestModel()]; | ||
}, | ||
); | ||
}, | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @param Closure(static):void $assert | ||
* @dataProvider data | ||
*/ | ||
public function test(Closure $assert): void | ||
{ | ||
$assert($this); | ||
} | ||
|
||
public function assert(ChunkWriteStateEntity $expected, Closure $data): void | ||
{ | ||
$state = (new ChunkWriteService())->write($data); | ||
Assert::assertEquals($expected, $state); | ||
} | ||
} |
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,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\LaraStrict\Unit\Database\Services; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
|
||
final class TestModel extends Model | ||
{ | ||
public static function insertOrIgnore(array $data): int | ||
{ | ||
return count($data); | ||
} | ||
} |