|
10 | 10 | use Doctrine\Deprecations\Deprecation;
|
11 | 11 |
|
12 | 12 | use function assert;
|
| 13 | +use function fseek; |
| 14 | +use function ftell; |
13 | 15 | use function func_num_args;
|
14 | 16 | use function is_int;
|
| 17 | +use function is_resource; |
15 | 18 | use function sqlsrv_execute;
|
16 | 19 | use function SQLSRV_PHPTYPE_STREAM;
|
17 | 20 | use function SQLSRV_PHPTYPE_STRING;
|
18 | 21 | use function sqlsrv_prepare;
|
19 | 22 | use function SQLSRV_SQLTYPE_VARBINARY;
|
| 23 | +use function stream_get_meta_data; |
20 | 24 | use function stripos;
|
21 | 25 |
|
| 26 | +use const SEEK_SET; |
22 | 27 | use const SQLSRV_ENC_BINARY;
|
23 | 28 | use const SQLSRV_ENC_CHAR;
|
24 | 29 | use const SQLSRV_PARAM_IN;
|
@@ -58,6 +63,13 @@ final class Statement implements StatementInterface
|
58 | 63 | */
|
59 | 64 | private array $types = [];
|
60 | 65 |
|
| 66 | + /** |
| 67 | + * Resources used as bound values. |
| 68 | + * |
| 69 | + * @var mixed[]|null |
| 70 | + */ |
| 71 | + private ?array $paramResources = null; |
| 72 | + |
61 | 73 | /**
|
62 | 74 | * Append to any INSERT query to retrieve the last insert id.
|
63 | 75 | */
|
@@ -97,6 +109,10 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
|
97 | 109 | );
|
98 | 110 | }
|
99 | 111 |
|
| 112 | + if ($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY) { |
| 113 | + $this->trackParamResource($value); |
| 114 | + } |
| 115 | + |
100 | 116 | $this->variables[$param] = $value;
|
101 | 117 | $this->types[$param] = $type;
|
102 | 118 |
|
@@ -159,10 +175,17 @@ public function execute($params = null): ResultInterface
|
159 | 175 | }
|
160 | 176 | }
|
161 | 177 |
|
162 |
| - $this->stmt ??= $this->prepare(); |
| 178 | + $resourceOffsets = $this->getResourceOffsets(); |
| 179 | + try { |
| 180 | + $this->stmt ??= $this->prepare(); |
163 | 181 |
|
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 | + } |
166 | 189 | }
|
167 | 190 |
|
168 | 191 | return new Result($this->stmt);
|
@@ -220,4 +243,73 @@ private function prepare()
|
220 | 243 |
|
221 | 244 | return $stmt;
|
222 | 245 | }
|
| 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 | + } |
223 | 315 | }
|
0 commit comments