Skip to content

Commit

Permalink
Merge pull request #128 from nextras/buffer
Browse files Browse the repository at this point in the history
Buffer
  • Loading branch information
hrach committed Nov 8, 2020
2 parents 0c2cd2e + 3b5bc72 commit 2354d2e
Show file tree
Hide file tree
Showing 16 changed files with 304 additions and 30 deletions.
13 changes: 13 additions & 0 deletions doc/result.texy
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ echo $row->age;

echo $row->getNthField(0); // prints name
\--


Buffering
=========

Some database drivers do not support rewinding or seeking the result. I.e. you cannot iterate over the result multiple times. Similarly, you cannot use `seek()` method to skip some rows. Dbal's emulated buffering comes to solve this for you. The relevant drivers automatically enable emulated buffering. You can disable or enable it for particular `Result` instances.

/--php
$result = $connection->query('...')->buffered(); // enable emulated buffering
$result->unbuffered(); // disable the emulated buffering
\--

If the unbuffered Result was already partially consumed, enabling buffering does nothing and Result will potentially throw an exception when rewinded or seeked. If the buffered Result was already partially consumed, disabling buffering does nothing and Result will still use the buffer.
14 changes: 13 additions & 1 deletion src/Drivers/Mysqli/MysqliEmptyResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Nextras\Dbal\Drivers\Mysqli;


use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;


Expand All @@ -13,6 +13,18 @@ class MysqliEmptyResultAdapter implements IResultAdapter
use StrictObjectTrait;


public function toBuffered(): IResultAdapter
{
return $this;
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
throw new InvalidArgumentException("Unable to seek in row set to {$index} index.");
Expand Down
14 changes: 13 additions & 1 deletion src/Drivers/Mysqli/MysqliResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


use mysqli_result;
use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;


Expand Down Expand Up @@ -63,6 +63,18 @@ public function __destruct()
}


public function toBuffered(): IResultAdapter
{
return $this;
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
if ($this->result->num_rows !== 0 && !$this->result->data_seek($index)) {
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/Pdo/PdoDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
use Nextras\Dbal\Drivers\Exception\ConnectionException;
use Nextras\Dbal\Drivers\Exception\DriverException;
use Nextras\Dbal\Drivers\IDriver;
use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidStateException;
use Nextras\Dbal\Exception\NotSupportedException;
use Nextras\Dbal\ILogger;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Result\Result;
use Nextras\Dbal\Utils\LoggerHelper;
use Nextras\Dbal\Utils\StrictObjectTrait;
Expand Down
4 changes: 2 additions & 2 deletions src/Drivers/PdoMysql/PdoMysqlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
use Nextras\Dbal\Drivers\Exception\QueryException;
use Nextras\Dbal\Drivers\Exception\UniqueConstraintViolationException;
use Nextras\Dbal\Drivers\IDriver;
use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Drivers\Pdo\PdoDriver;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Exception\NotSupportedException;
use Nextras\Dbal\ILogger;
use Nextras\Dbal\Platforms\IPlatform;
use Nextras\Dbal\Platforms\MySqlPlatform;
use Nextras\Dbal\Result\IResultAdapter;
use PDO;
use PDOStatement;
use function array_key_exists;
Expand Down Expand Up @@ -135,7 +135,7 @@ public function convertToPhp($value, $nativeType)

protected function createResultAdapter(PDOStatement $statement): IResultAdapter
{
return new PdoMysqlResultAdapter($statement);
return (new PdoMysqlResultAdapter($statement))->toBuffered();
}


Expand Down
23 changes: 19 additions & 4 deletions src/Drivers/PdoMysql/PdoMysqlResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace Nextras\Dbal\Drivers\PdoMysql;


use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidStateException;
use Nextras\Dbal\Exception\NotSupportedException;
use Nextras\Dbal\Result\BufferedResultAdapter;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;
use PDO;
use PDOStatement;
use function assert;


class PdoMysqlResultAdapter implements IResultAdapter
Expand Down Expand Up @@ -59,10 +59,25 @@ public function __construct(PDOStatement $statement)
}


public function toBuffered(): IResultAdapter
{
return new BufferedResultAdapter($this);
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
if ($index === 0 && $this->beforeFirstFetch) return;
throw new NotSupportedException("PDO does not support seek & replay. Use Result::fetchAll() to and result its result.");
if ($index === 0 && $this->beforeFirstFetch) {
return;
}

throw new NotSupportedException("PDO does not support rewinding or seeking. Use Result::buffered() before first consume of the result.");
}


Expand Down
4 changes: 2 additions & 2 deletions src/Drivers/PdoPgsql/PdoPgsqlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
use Nextras\Dbal\Drivers\Exception\QueryException;
use Nextras\Dbal\Drivers\Exception\UniqueConstraintViolationException;
use Nextras\Dbal\Drivers\IDriver;
use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Drivers\Pdo\PdoDriver;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Exception\InvalidStateException;
use Nextras\Dbal\Exception\NotSupportedException;
use Nextras\Dbal\ILogger;
use Nextras\Dbal\Platforms\IPlatform;
use Nextras\Dbal\Platforms\PostgreSqlPlatform;
use Nextras\Dbal\Result\IResultAdapter;
use PDOStatement;
use function array_map;
use function date;
Expand Down Expand Up @@ -110,7 +110,7 @@ public function convertToPhp($value, $nativeType)

protected function createResultAdapter(PDOStatement $statement): IResultAdapter
{
return new PdoPgsqlResultAdapter($statement);
return (new PdoPgsqlResultAdapter($statement))->toBuffered();
}


Expand Down
23 changes: 19 additions & 4 deletions src/Drivers/PdoPgsql/PdoPgsqlResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace Nextras\Dbal\Drivers\PdoPgsql;


use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidStateException;
use Nextras\Dbal\Exception\NotSupportedException;
use Nextras\Dbal\Result\BufferedResultAdapter;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;
use PDO;
use PDOStatement;
use function assert;


class PdoPgsqlResultAdapter implements IResultAdapter
Expand Down Expand Up @@ -55,10 +55,25 @@ public function __construct(PDOStatement $statement)
}


public function toBuffered(): IResultAdapter
{
return new BufferedResultAdapter($this);
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
if ($index === 0 && $this->beforeFirstFetch) return;
throw new NotSupportedException("PDO does not support seek & replay. Use Result::fetchAll() to and result its result.");
if ($index === 0 && $this->beforeFirstFetch) {
return;
}

throw new NotSupportedException("PDO does not support rewinding or seeking. Use Result::buffered() before first consume of the result.");
}


Expand Down
14 changes: 13 additions & 1 deletion src/Drivers/Pgsql/PgsqlResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Nextras\Dbal\Drivers\Pgsql;


use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;
use function pg_fetch_array;
use function pg_field_name;
Expand Down Expand Up @@ -70,6 +70,18 @@ public function __destruct()
}


public function toBuffered(): IResultAdapter
{
return $this;
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
if (pg_num_rows($this->result) !== 0 && !pg_result_seek($this->result, $index)) {
Expand Down
14 changes: 13 additions & 1 deletion src/Drivers/Sqlsrv/SqlsrvResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Nextras\Dbal\Drivers\Sqlsrv;


use Nextras\Dbal\Drivers\IResultAdapter;
use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\Result\IResultAdapter;
use Nextras\Dbal\Utils\StrictObjectTrait;
use function sqlsrv_fetch;
use function sqlsrv_fetch_array;
Expand Down Expand Up @@ -55,6 +55,18 @@ public function __destruct()
}


public function toBuffered(): IResultAdapter
{
return $this;
}


public function toUnbuffered(): IResultAdapter
{
return $this;
}


public function seek(int $index): void
{
if ($index !== 0 && sqlsrv_num_rows($this->statement) !== 0 && sqlsrv_fetch($this->statement, SQLSRV_SCROLL_ABSOLUTE, $index) !== true) {
Expand Down
101 changes: 101 additions & 0 deletions src/Result/BufferedResultAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php declare(strict_types = 1);

namespace Nextras\Dbal\Result;


use ArrayIterator;
use Nextras\Dbal\Exception\InvalidArgumentException;
use OutOfBoundsException;
use function assert;


class BufferedResultAdapter implements IResultAdapter
{
/** @var IResultAdapter */
private $adapter;

/** @var ArrayIterator<mixed, mixed>|null */
private $data;


public function __construct(IResultAdapter $adapter)
{
$this->adapter = $adapter;
}


public function toBuffered(): IResultAdapter
{
return $this;
}


public function toUnbuffered(): IResultAdapter
{
if ($this->data === null) {
return $this->adapter->toUnbuffered();
} else {
return $this;
}
}


public function seek(int $index): void
{
if ($this->data === null) {
$this->init();
}
assert($this->data !== null);

if ($index === 0) {
$this->data->rewind();
return;
}

try {
$this->data->seek($index);
} catch (OutOfBoundsException $e) {
throw new InvalidArgumentException("Unable to seek in row set to {$index} index.", 0, $e);
}
}


public function fetch(): ?array
{
if ($this->data === null) {
$this->init();
}
assert($this->data !== null);

$fetched = $this->data->valid() ? $this->data->current() : null;
$this->data->next();
return $fetched;
}


public function getTypes(): array
{
return $this->adapter->getTypes();
}


public function getRowsCount(): int
{
if ($this->data === null) {
$this->init();
}
assert($this->data !== null);

return $this->data->count();
}


private function init(): void
{
$rows = [];
while (($row = $this->adapter->fetch()) !== null) {
$rows[] = $row;
}
$this->data = new ArrayIterator($rows);
}
}
Loading

0 comments on commit 2354d2e

Please sign in to comment.