Start benzina db streaming tool
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina\Pdo;
|
||||
|
||||
use App\Library\Benzina\ReaderInterface;
|
||||
use App\Library\Benzina\Stream;
|
||||
|
||||
class PdoReader implements ReaderInterface
|
||||
{
|
||||
private \PDO $db;
|
||||
|
||||
/**
|
||||
* @var array{name: string, schema: string, host: string, user: string, pass: string}
|
||||
*/
|
||||
public readonly array $config;
|
||||
|
||||
public function __construct(string $databaseUrl)
|
||||
{
|
||||
$parsedUrl = parse_url($databaseUrl);
|
||||
$dbdata = [
|
||||
'name' => ltrim($parsedUrl['path'], '/'),
|
||||
...$parsedUrl
|
||||
];
|
||||
|
||||
$this->db = new \PDO(
|
||||
dsn: sprintf("%s:host=%s;dbname=%s", $dbdata['scheme'], $dbdata['host'], $dbdata['name']),
|
||||
username: $dbdata['user'],
|
||||
password: $dbdata['pass'],
|
||||
options: [
|
||||
\PDO::ATTR_EMULATE_PREPARES => false,
|
||||
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
|
||||
$this->config = $dbdata;
|
||||
}
|
||||
|
||||
public function get(string $name): PdoStream
|
||||
{
|
||||
$count = $this->db->query("SELECT COUNT(*) FROM `$name`;")->fetchColumn();
|
||||
$query = $this->db->prepare("SELECT * FROM `$name` LIMIT ? OFFSET ?;");
|
||||
|
||||
$stream = new Stream(str_repeat("\n", $count));
|
||||
return new PdoStream($stream, $query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina\Pdo;
|
||||
|
||||
use App\Library\Benzina\Stream;
|
||||
use App\Library\Benzina\StreamInterface;
|
||||
|
||||
class PdoStream implements StreamInterface
|
||||
{
|
||||
private const BATCH_SIZE = 99;
|
||||
private int $currentBatch = 0;
|
||||
|
||||
public function __construct(
|
||||
private Stream $stream,
|
||||
private \PDOStatement $pdo,
|
||||
) {
|
||||
}
|
||||
|
||||
public function eof(): bool
|
||||
{
|
||||
return $this->stream->eof();
|
||||
}
|
||||
|
||||
public function read(?int $length = null): mixed
|
||||
{
|
||||
while (!$this->stream->eof()) {
|
||||
$this->stream->read(self::BATCH_SIZE);
|
||||
$this->pdo->execute([self::BATCH_SIZE, $this->currentBatch]);
|
||||
|
||||
$result = $this->pdo->fetchAll();
|
||||
$this->currentBatch += self::BATCH_SIZE;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->stream->close();
|
||||
}
|
||||
|
||||
public function length(): int
|
||||
{
|
||||
return $this->stream->length();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina;
|
||||
|
||||
interface ReaderInterface
|
||||
{
|
||||
public function get(string $entityName): StreamInterface;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina;
|
||||
|
||||
class Stream implements StreamInterface
|
||||
{
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
|
||||
public function __construct(string $data)
|
||||
{
|
||||
try {
|
||||
$this->stream = \fopen('php://temp', 'r+');
|
||||
|
||||
\fwrite($this->stream, $data);
|
||||
\fseek($this->stream, 0);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException("Unable to open stream");
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function eof(): bool
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException(self::MESSAGE_ERROR_DETACHED);
|
||||
}
|
||||
|
||||
return \feof($this->stream);
|
||||
}
|
||||
|
||||
public function read(?int $length = null): string
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException(self::MESSAGE_ERROR_DETACHED);
|
||||
}
|
||||
|
||||
if ($length < 0) {
|
||||
throw new \RuntimeException("Length parameter cannot be negative");
|
||||
}
|
||||
|
||||
if ($length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$string = \fgets($this->stream, $length);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(self::MESSAGE_ERROR_UNABLE_TO_READ, 0, $e);
|
||||
}
|
||||
|
||||
if ($string === false) {
|
||||
throw new \RuntimeException(self::MESSAGE_ERROR_UNABLE_TO_READ);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
if (isset($this->stream)) {
|
||||
if (\is_resource($this->stream)) {
|
||||
\fclose($this->stream);
|
||||
}
|
||||
|
||||
unset($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
public function length(): int
|
||||
{
|
||||
if (!isset($this->stream)) {
|
||||
throw new \RuntimeException(self::MESSAGE_ERROR_DETACHED);
|
||||
}
|
||||
|
||||
return \fstat($this->stream)['size'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina;
|
||||
|
||||
interface StreamInterface
|
||||
{
|
||||
public const MESSAGE_ERROR_DETACHED = 'Stream is detached';
|
||||
public const MESSAGE_ERROR_UNABLE_TO_READ = 'Unable to read from stream';
|
||||
|
||||
public function eof(): bool;
|
||||
|
||||
public function read(?int $length = null): mixed;
|
||||
|
||||
public function close(): void;
|
||||
|
||||
public function length(): int;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina;
|
||||
|
||||
interface WriterInterface
|
||||
{
|
||||
public function process(ReaderInterface $reader);
|
||||
}
|
||||
Reference in New Issue
Block a user