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 3 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
26 changes: 25 additions & 1 deletion 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 @@ -213,8 +217,28 @@
*/
private function copyStreamToStream($source, $target): void
{
$resetTo = false;
if (stream_get_meta_data($source)['seekable']) {
$resetTo = ftell($source);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L220-L222

Added lines #L220 - L222 were not covered by tests
}

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 227 in src/Driver/IBMDB2/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L226-L227

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L229-L230

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

throw CannotCopyStreamToStream::new($copyToStreamError);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L235

Added line #L235 was not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L238-L239

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

fseek($source, $resetTo, SEEK_SET);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/IBMDB2/Statement.php#L242

Added line #L242 was not covered by tests
}
}
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 @@ -213,15 +218,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);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L221-L223

Added lines #L221 - L223 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L227-L228

Added lines #L227 - L228 were not covered by tests

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 231 in src/Driver/Mysqli/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L230-L231

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/Mysqli/Statement.php#L234-L235

Added lines #L234 - L235 were not covered by tests
}
}
} finally {
if ($resetTo !== false) {
fseek($stream, $resetTo, SEEK_SET);

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#L239-L240

Added lines #L239 - L240 were not covered by tests
}
}
}
Expand Down
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 @@
/** @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($value);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L76-L77

Added lines #L76 - L77 were not covered by tests
}

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

Expand Down Expand Up @@ -164,11 +176,87 @@
$mode = OCI_NO_AUTO_COMMIT;
}

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L179

Added line #L179 was not covered by tests
try {
$ret = @oci_execute($this->statement, $mode);
if (! $ret) {
throw Error::new($this->statement);

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L181-L183

Added lines #L181 - L183 were not covered by tests
}
} 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#L186-L187

Added lines #L186 - L187 were 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 mixed $resource
*/
private function trackParamResource($resource): void

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L200

Added line #L200 was not covered by tests
{
if (! is_resource($resource)) {
return;

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L202-L203

Added lines #L202 - L203 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L206 - L207 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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L217

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L219-L220

Added lines #L219 - L220 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L223-L227

Added lines #L223 - L227 were not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

return $resourceOffsets;

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L242

Added line #L242 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 250 in src/Driver/OCI8/Statement.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L250

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L256 - L257 were not covered by tests
}

$this->paramResources = null;

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

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Statement.php#L260

Added line #L260 was not covered by tests
}
}
98 changes: 98 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,94 @@
);
}

if ($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY) {
$this->trackParamResource($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 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;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L176 was not covered by tests
}

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L184 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 198 in src/Driver/PDO/SQLSrv/Statement.php

View check run for this annotation

Codecov / codecov/patch

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

Added line #L198 was not covered by tests
}

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

$this->paramResources = null;
}
}
Loading
Loading