Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore binary offsets of PDOStatement parameters #5897

Open
wants to merge 7 commits into
base: 3.9.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/Driver/IBMDB2/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use Throwable;

use function assert;
use function db2_bind_param;
use function db2_execute;
use function error_get_last;
use function fclose;
use function fseek;
use function ftell;
use function func_num_args;
use function is_int;
use function is_resource;
Expand All @@ -28,6 +31,7 @@
use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
use const SEEK_SET;

final class Statement implements StatementInterface
{
Expand Down Expand Up @@ -153,7 +157,13 @@
fclose($handle);
}

$this->lobs = [];
// Clear parameters bound to closed handles.
// $this->lobs itself is not cleared, allowing a
// second call to execute without rebinding, which
// is supported for other parameter types.
foreach ($this->lobs as $param => $value) {
unset($this->parameters[$param]);
}

if ($result === false) {
throw StatementError::new($this->stmt);
Expand Down Expand Up @@ -213,8 +223,28 @@
*/
private function copyStreamToStream($source, $target): void
{
$resetTo = false;
if (stream_get_meta_data($source)['seekable']) {
$resetTo = ftell($source);
}

if (@stream_copy_to_stream($source, $target) === false) {
throw CannotCopyStreamToStream::new(error_get_last());
$copyToStreamError = error_get_last();
if ($resetTo !== false) {

Check warning on line 233 in src/Driver/IBMDB2/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L232-L233

Added lines #L232 - L233 were not covered by tests
try {
fseek($source, $resetTo, SEEK_SET);
} catch (Throwable $e) {

Check warning on line 236 in src/Driver/IBMDB2/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L235-L236

Added lines #L235 - L236 were not covered by tests
// Swallow, we want the original exception from stream_copy_to_stream
}
}

throw CannotCopyStreamToStream::new($copyToStreamError);

Check warning on line 241 in src/Driver/IBMDB2/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L241

Added line #L241 was not covered by tests
}

if ($resetTo === false) {
return;

Check warning on line 245 in src/Driver/IBMDB2/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L245

Added line #L245 was not covered by tests
}

fseek($source, $resetTo, SEEK_SET);
}
}
30 changes: 23 additions & 7 deletions src/Driver/Mysqli/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@
use function count;
use function feof;
use function fread;
use function fseek;
use function ftell;
use function func_num_args;
use function get_resource_type;
use function is_int;
use function is_resource;
use function str_repeat;
use function stream_get_meta_data;

use const SEEK_SET;

final class Statement implements StatementInterface
{
Expand Down Expand Up @@ -218,15 +223,26 @@
private function sendLongData(array $streams): void
{
foreach ($streams as $paramNr => $stream) {
while (! feof($stream)) {
$chunk = fread($stream, 8192);
$resetTo = false;
if (stream_get_meta_data($stream)['seekable']) {
$resetTo = ftell($stream);
}

if ($chunk === false) {
throw FailedReadingStreamOffset::new($paramNr);
}
try {
while (! feof($stream)) {
$chunk = fread($stream, 8192);

if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) {
throw StatementError::new($this->stmt);
if ($chunk === false) {
throw FailedReadingStreamOffset::new($paramNr);

Check warning on line 236 in src/Driver/Mysqli/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L236

Added line #L236 was not covered by tests
}

if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) {
throw StatementError::new($this->stmt);

Check warning on line 240 in src/Driver/Mysqli/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L240

Added line #L240 was not covered by tests
}
}
} finally {
if ($resetTo !== false) {
fseek($stream, $resetTo, SEEK_SET);
}
}
}
Expand Down
93 changes: 90 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 @@
/** @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 @@
);
}

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

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

Expand Down Expand Up @@ -164,11 +176,86 @@
$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);

Check warning on line 187 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L187

Added line #L187 was not covered by tests
}
}

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

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

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

Check warning on line 208 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L207-L208

Added lines #L207 - L208 were not covered by tests
}

/**
* 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);

Check warning on line 228 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L224-L228

Added lines #L224 - L228 were not covered by tests
}

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

Check warning on line 232 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L231-L232

Added lines #L231 - L232 were not covered by tests
}

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

Check warning on line 236 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L235-L236

Added lines #L235 - L236 were not covered by tests
}

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

Check warning on line 240 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L239-L240

Added lines #L239 - L240 were not covered by tests
}

return $resourceOffsets;

Check warning on line 243 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L243

Added line #L243 was not covered by tests
}

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

Check warning on line 251 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L251

Added line #L251 was not covered by tests
{
if ($resourceOffsets === null || $this->paramResources === null) {
return;

Check warning on line 254 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L253-L254

Added lines #L253 - L254 were not covered by tests
}

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

Check warning on line 258 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L257-L258

Added lines #L257 - L258 were not covered by tests
}
}
}
97 changes: 97 additions & 0 deletions src/Driver/PDO/SQLSrv/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
use Doctrine\DBAL\Driver\Exception\UnknownParameterType;
use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;

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

use const SEEK_SET;

final class Statement extends AbstractStatementMiddleware
{
private PDOStatement $statement;

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

/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(PDOStatement $statement)
{
Expand Down Expand Up @@ -104,6 +114,93 @@
);
}

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

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

/**
* {@inheritDoc}
*/
public function execute($params = null): Result
{
$resourceOffsets = $this->getResourceOffsets();
try {
return parent::execute($params);
} finally {
if ($resourceOffsets !== null) {
$this->restoreResourceOffsets($resourceOffsets);
}
}
}

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

$this->paramResources ??= [];
$this->paramResources[$param] = $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;

Check warning on line 177 in src/Driver/PDO/SQLSrv/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/PDO/SQLSrv/Statement.php#L177

Added line #L177 was not covered by tests
}

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

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

Check warning on line 185 in src/Driver/PDO/SQLSrv/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/PDO/SQLSrv/Statement.php#L185

Added line #L185 was not covered by tests
}

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;

Check warning on line 199 in src/Driver/PDO/SQLSrv/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/PDO/SQLSrv/Statement.php#L199

Added line #L199 was not covered by tests
}

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