Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8932340

Browse files
mcurlandderrabus
authored andcommittedNov 24, 2024·
SQLSrv, restore driver resource offsets
Adding drivers to completed set.
1 parent ca8e125 commit 8932340

File tree

2 files changed

+193
-3
lines changed

2 files changed

+193
-3
lines changed
 

‎src/Driver/PDO/SQLSrv/Statement.php

+98
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,26 @@
55
use Doctrine\DBAL\Driver\Exception\UnknownParameterType;
66
use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
77
use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement;
8+
use Doctrine\DBAL\Driver\Result;
89
use Doctrine\DBAL\ParameterType;
910
use Doctrine\Deprecations\Deprecation;
1011
use PDO;
1112

13+
use function fseek;
14+
use function ftell;
1215
use function func_num_args;
16+
use function is_resource;
17+
use function stream_get_meta_data;
18+
19+
use const SEEK_SET;
1320

1421
final class Statement extends AbstractStatementMiddleware
1522
{
1623
private PDOStatement $statement;
1724

25+
/** @var mixed[]|null */
26+
private ?array $paramResources = null;
27+
1828
/** @internal The statement can be only instantiated by its driver connection. */
1929
public function __construct(PDOStatement $statement)
2030
{
@@ -104,6 +114,94 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
104114
);
105115
}
106116

117+
if ($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY) {
118+
$this->trackParamResource($value);
119+
}
120+
107121
return $this->bindParam($param, $value, $type);
108122
}
123+
124+
/**
125+
* {@inheritDoc}
126+
*/
127+
public function execute($params = null): Result
128+
{
129+
$resourceOffsets = $this->getResourceOffsets();
130+
try {
131+
return parent::execute($params);
132+
} finally {
133+
if ($resourceOffsets !== null) {
134+
$this->restoreResourceOffsets($resourceOffsets);
135+
}
136+
}
137+
}
138+
139+
/**
140+
* Track a binary parameter reference at binding time. These
141+
* are cached for later analysis by the getResourceOffsets.
142+
*
143+
* @param mixed $resource
144+
*/
145+
private function trackParamResource($resource): void
146+
{
147+
if (! is_resource($resource)) {
148+
return;
149+
}
150+
151+
$this->paramResources ??= [];
152+
$this->paramResources[] = $resource;
153+
}
154+
155+
/**
156+
* Determine the offset that any resource parameters needs to be
157+
* restored to after the statement is executed. Call immediately
158+
* before execute (not during bindValue) to get the most accurate offset.
159+
*
160+
* @return int[]|null Return offsets to restore if needed. The array may be sparse.
161+
*/
162+
private function getResourceOffsets(): ?array
163+
{
164+
if ($this->paramResources === null) {
165+
return null;
166+
}
167+
168+
$resourceOffsets = null;
169+
foreach ($this->paramResources as $index => $resource) {
170+
$position = false;
171+
if (stream_get_meta_data($resource)['seekable']) {
172+
$position = ftell($resource);
173+
}
174+
175+
if ($position === false) {
176+
continue;
177+
}
178+
179+
$resourceOffsets ??= [];
180+
$resourceOffsets[$index] = $position;
181+
}
182+
183+
if ($resourceOffsets === null) {
184+
$this->paramResources = null;
185+
}
186+
187+
return $resourceOffsets;
188+
}
189+
190+
/**
191+
* Restore resource offsets moved by PDOStatement->execute
192+
*
193+
* @param int[]|null $resourceOffsets The offsets returned by getResourceOffsets.
194+
*/
195+
private function restoreResourceOffsets(?array $resourceOffsets): void
196+
{
197+
if ($resourceOffsets === null || $this->paramResources === null) {
198+
return;
199+
}
200+
201+
foreach ($resourceOffsets as $index => $offset) {
202+
fseek($this->paramResources[$index], $offset, SEEK_SET);
203+
}
204+
205+
$this->paramResources = null;
206+
}
109207
}

‎src/Driver/SQLSrv/Statement.php

+95-3
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@
1010
use Doctrine\Deprecations\Deprecation;
1111

1212
use function assert;
13+
use function fseek;
14+
use function ftell;
1315
use function func_num_args;
1416
use function is_int;
17+
use function is_resource;
1518
use function sqlsrv_execute;
1619
use function SQLSRV_PHPTYPE_STREAM;
1720
use function SQLSRV_PHPTYPE_STRING;
1821
use function sqlsrv_prepare;
1922
use function SQLSRV_SQLTYPE_VARBINARY;
23+
use function stream_get_meta_data;
2024
use function stripos;
2125

26+
use const SEEK_SET;
2227
use const SQLSRV_ENC_BINARY;
2328
use const SQLSRV_ENC_CHAR;
2429
use const SQLSRV_PARAM_IN;
@@ -58,6 +63,13 @@ final class Statement implements StatementInterface
5863
*/
5964
private array $types = [];
6065

66+
/**
67+
* Resources used as bound values.
68+
*
69+
* @var mixed[]|null
70+
*/
71+
private ?array $paramResources = null;
72+
6173
/**
6274
* Append to any INSERT query to retrieve the last insert id.
6375
*/
@@ -97,6 +109,10 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
97109
);
98110
}
99111

112+
if ($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY) {
113+
$this->trackParamResource($value);
114+
}
115+
100116
$this->variables[$param] = $value;
101117
$this->types[$param] = $type;
102118

@@ -159,10 +175,17 @@ public function execute($params = null): ResultInterface
159175
}
160176
}
161177

162-
$this->stmt ??= $this->prepare();
178+
$resourceOffsets = $this->getResourceOffsets();
179+
try {
180+
$this->stmt ??= $this->prepare();
163181

164-
if (! sqlsrv_execute($this->stmt)) {
165-
throw Error::new();
182+
if (! sqlsrv_execute($this->stmt)) {
183+
throw Error::new();
184+
}
185+
} finally {
186+
if ($resourceOffsets !== null) {
187+
$this->restoreResourceOffsets($resourceOffsets);
188+
}
166189
}
167190

168191
return new Result($this->stmt);
@@ -220,4 +243,73 @@ private function prepare()
220243

221244
return $stmt;
222245
}
246+
247+
/**
248+
* Track a binary parameter reference at binding time. These
249+
* are cached for later analysis by the getResourceOffsets.
250+
*
251+
* @param mixed $resource
252+
*/
253+
private function trackParamResource($resource): void
254+
{
255+
if (! is_resource($resource)) {
256+
return;
257+
}
258+
259+
$this->paramResources ??= [];
260+
$this->paramResources[] = $resource;
261+
}
262+
263+
/**
264+
* Determine the offset that any resource parameters needs to be
265+
* restored to after the statement is executed. Call immediately
266+
* before execute (not during bindValue) to get the most accurate offset.
267+
*
268+
* @return int[]|null Return offsets to restore if needed. The array may be sparse.
269+
*/
270+
private function getResourceOffsets(): ?array
271+
{
272+
if ($this->paramResources === null) {
273+
return null;
274+
}
275+
276+
$resourceOffsets = null;
277+
foreach ($this->paramResources as $index => $resource) {
278+
$position = false;
279+
if (stream_get_meta_data($resource)['seekable']) {
280+
$position = ftell($resource);
281+
}
282+
283+
if ($position === false) {
284+
continue;
285+
}
286+
287+
$resourceOffsets ??= [];
288+
$resourceOffsets[$index] = $position;
289+
}
290+
291+
if ($resourceOffsets === null) {
292+
$this->paramResources = null;
293+
}
294+
295+
return $resourceOffsets;
296+
}
297+
298+
/**
299+
* Restore resource offsets moved by PDOStatement->execute
300+
*
301+
* @param int[]|null $resourceOffsets The offsets returned by getResourceOffsets.
302+
*/
303+
private function restoreResourceOffsets(?array $resourceOffsets): void
304+
{
305+
if ($resourceOffsets === null || $this->paramResources === null) {
306+
return;
307+
}
308+
309+
foreach ($resourceOffsets as $index => $offset) {
310+
fseek($this->paramResources[$index], $offset, SEEK_SET);
311+
}
312+
313+
$this->paramResources = null;
314+
}
223315
}

0 commit comments

Comments
 (0)
Please sign in to comment.