Skip to content

Commit

Permalink
SQLite3 and OCI8, restore driver resource offsets
Browse files Browse the repository at this point in the history
Adding drivers to completed set.
  • Loading branch information
mcurland authored and derrabus committed Nov 24, 2024
1 parent 35c7cee commit 46dbfc2
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 4 deletions.
94 changes: 91 additions & 3 deletions src/Driver/OCI8/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,23 @@
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;

use function fseek;
use function ftell;
use function func_num_args;
use function is_int;
use function is_resource;
use function oci_bind_by_name;
use function oci_execute;
use function oci_new_descriptor;
use function stream_get_meta_data;

use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_COMMIT_ON_SUCCESS;
use const OCI_D_LOB;
use const OCI_NO_AUTO_COMMIT;
use const OCI_TEMP_BLOB;
use const SEEK_SET;
use const SQLT_CHR;

final class Statement implements StatementInterface
Expand All @@ -34,6 +39,9 @@ final class Statement implements StatementInterface
/** @var array<int,string> */
private array $parameterMap;

/** @var mixed[]|null */
private ?array $paramResources = null;

private ExecutionMode $executionMode;

/**
Expand Down Expand Up @@ -65,6 +73,10 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
);
}

if ($type === ParameterType::BINARY || $type === ParameterType::LARGE_OBJECT) {
$this->trackParamResource($value);
}

return $this->bindParam($param, $value, $type);
}

Expand Down Expand Up @@ -164,11 +176,87 @@ public function execute($params = null): ResultInterface
$mode = OCI_NO_AUTO_COMMIT;
}

$ret = @oci_execute($this->statement, $mode);
if (! $ret) {
throw Error::new($this->statement);
$resourceOffsets = $this->getResourceOffsets();
try {
$ret = @oci_execute($this->statement, $mode);
if (! $ret) {
throw Error::new($this->statement);
}
} finally {
if ($resourceOffsets !== null) {
$this->restoreResourceOffsets($resourceOffsets);
}
}

return new Result($this->statement);
}

/**
* Track a binary parameter reference at binding time. These
* are cached for later analysis by the getResourceOffsets.
*
* @param mixed $resource
*/
private function trackParamResource($resource): void
{
if (! is_resource($resource)) {
return;
}

$this->paramResources ??= [];
$this->paramResources[] = $resource;
}

/**
* Determine the offset that any resource parameters needs to be
* restored to after the statement is executed. Call immediately
* before execute (not during bindValue) to get the most accurate offset.
*
* @return int[]|null Return offsets to restore if needed. The array may be sparse.
*/
private function getResourceOffsets(): ?array
{
if ($this->paramResources === null) {
return null;
}

$resourceOffsets = null;
foreach ($this->paramResources as $index => $resource) {
$position = false;
if (stream_get_meta_data($resource)['seekable']) {
$position = ftell($resource);
}

if ($position === false) {
continue;
}

$resourceOffsets ??= [];
$resourceOffsets[$index] = $position;
}

if ($resourceOffsets === null) {
$this->paramResources = null;
}

return $resourceOffsets;
}

/**
* Restore resource offsets moved by PDOStatement->execute
*
* @param int[]|null $resourceOffsets The offsets returned by getResourceOffsets.
*/
private function restoreResourceOffsets(?array $resourceOffsets): void
{
if ($resourceOffsets === null || $this->paramResources === null) {
return;
}

foreach ($resourceOffsets as $index => $offset) {
fseek($this->paramResources[$index], $offset, SEEK_SET);
}

$this->paramResources = null;
}
}
89 changes: 88 additions & 1 deletion src/Driver/SQLite3/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@
use SQLite3Stmt;

use function assert;
use function fseek;
use function ftell;
use function func_num_args;
use function is_int;
use function is_resource;
use function stream_get_meta_data;

use const SEEK_SET;
use const SQLITE3_BLOB;
use const SQLITE3_INTEGER;
use const SQLITE3_NULL;
Expand All @@ -33,6 +38,9 @@ final class Statement implements StatementInterface
private SQLite3 $connection;
private SQLite3Stmt $statement;

/** @var mixed[]|null */
private ?array $paramResources = null;

/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(SQLite3 $connection, SQLite3Stmt $statement)
{
Expand All @@ -58,7 +66,12 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
);
}

return $this->statement->bindValue($param, $value, $this->convertParamType($type));
$sqliteType = $this->convertParamType($type);
if ($sqliteType === SQLITE3_BLOB) {
$this->trackParamResource($value);
}

return $this->statement->bindValue($param, $value, $sqliteType);
}

/**
Expand Down Expand Up @@ -109,10 +122,15 @@ public function execute($params = null): Result
}
}

$resourceOffsets = $this->getResourceOffsets();
try {
$result = $this->statement->execute();
} catch (\Exception $e) {
throw Exception::new($e);
} finally {
if ($resourceOffsets !== null) {
$this->restoreResourceOffsets($resourceOffsets);
}
}

assert($result !== false);
Expand All @@ -133,4 +151,73 @@ private function convertParamType(int $type): int

return self::PARAM_TYPE_MAP[$type];
}

/**
* Track a binary parameter reference at binding time. These
* are cached for later analysis by the getResourceOffsets.
*
* @param mixed $resource
*/
private function trackParamResource($resource): void
{
if (! is_resource($resource)) {
return;
}

$this->paramResources ??= [];
$this->paramResources[] = $resource;
}

/**
* Determine the offset that any resource parameters needs to be
* restored to after the statement is executed. Call immediately
* before execute (not during bindValue) to get the most accurate offset.
*
* @return int[]|null Return offsets to restore if needed. The array may be sparse.
*/
private function getResourceOffsets(): ?array
{
if ($this->paramResources === null) {
return null;
}

$resourceOffsets = null;
foreach ($this->paramResources as $index => $resource) {
$position = false;
if (stream_get_meta_data($resource)['seekable']) {
$position = ftell($resource);
}

if ($position === false) {
continue;
}

$resourceOffsets ??= [];
$resourceOffsets[$index] = $position;
}

if ($resourceOffsets === null) {
$this->paramResources = null;
}

return $resourceOffsets;
}

/**
* Restore resource offsets moved by PDOStatement->execute
*
* @param int[]|null $resourceOffsets The offsets returned by getResourceOffsets.
*/
private function restoreResourceOffsets(?array $resourceOffsets): void
{
if ($resourceOffsets === null || $this->paramResources === null) {
return;
}

foreach ($resourceOffsets as $index => $offset) {
fseek($this->paramResources[$index], $offset, SEEK_SET);
}

$this->paramResources = null;
}
}

0 comments on commit 46dbfc2

Please sign in to comment.