Skip to content

Commit dcac149

Browse files
committed
fix(client): close error response bodies
1 parent 842afc0 commit dcac149

2 files changed

Lines changed: 103 additions & 5 deletions

File tree

src/Client/PsrClickHouseClient.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,13 @@ private function sendHttpRequest(
381381
}
382382

383383
if ($response->getStatusCode() !== 200) {
384-
throw ServerError::fromResponse($response);
384+
$body = $response->getBody();
385+
386+
try {
387+
throw ServerError::fromResponse($response);
388+
} finally {
389+
$body->close();
390+
}
385391
}
386392

387393
if (! $detectStreamedException) {
@@ -390,9 +396,13 @@ private function sendHttpRequest(
390396

391397
$body = $response->getBody();
392398
if (! $body->isSeekable()) {
393-
throw new RuntimeException(
394-
'Cannot inspect streamed ClickHouse exceptions on a non-seekable response body.',
395-
);
399+
try {
400+
throw new RuntimeException(
401+
'Cannot inspect streamed ClickHouse exceptions on a non-seekable response body.',
402+
);
403+
} finally {
404+
$body->close();
405+
}
396406
}
397407

398408
$bodyContent = $body->__toString();
@@ -402,7 +412,11 @@ private function sendHttpRequest(
402412
$response->getHeaderLine('X-ClickHouse-Exception-Tag'),
403413
)
404414
) {
405-
throw ServerError::fromResponse($response);
415+
try {
416+
throw ServerError::fromResponseContent($bodyContent, $response->getStatusCode());
417+
} finally {
418+
$body->close();
419+
}
406420
}
407421

408422
Message::rewindBody($response);

tests/Client/PsrClickHouseClientStreamedExceptionTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,90 @@ public function sendRequest(RequestInterface $request): ResponseInterface
218218
$client->executeQuery('OPTIMIZE TABLE events');
219219
}
220220

221+
public function testExecuteQueryClosesNonOkResponseBody(): void
222+
{
223+
$psr17Factory = new Psr17Factory();
224+
225+
$body = $this->createMock(StreamInterface::class);
226+
$body->expects(self::once())
227+
->method('__toString')
228+
->willReturn('Code: 60. DB::Exception: Table events does not exist. (UNKNOWN_TABLE)');
229+
$body->expects(self::once())
230+
->method('close');
231+
232+
$response = $psr17Factory->createResponse(404)
233+
->withBody($body);
234+
235+
$httpClient = new class ($response) implements ClientInterface {
236+
public function __construct(private ResponseInterface $response)
237+
{
238+
}
239+
240+
public function sendRequest(RequestInterface $request): ResponseInterface
241+
{
242+
return $this->response;
243+
}
244+
};
245+
246+
$client = new PsrClickHouseClient(
247+
$httpClient,
248+
new RequestFactory(
249+
new ParamValueConverterRegistry(),
250+
$psr17Factory,
251+
$psr17Factory,
252+
$psr17Factory,
253+
),
254+
);
255+
256+
$this->expectException(ServerError::class);
257+
258+
$client->executeQuery('OPTIMIZE TABLE events');
259+
}
260+
261+
public function testSelectClosesResponseBodyWhenOkResponseContainsStreamedException(): void
262+
{
263+
$psr17Factory = new Psr17Factory();
264+
265+
$body = $this->createMock(StreamInterface::class);
266+
$body->expects(self::once())
267+
->method('isSeekable')
268+
->willReturn(true);
269+
$body->expects(self::once())
270+
->method('__toString')
271+
->willReturn(self::streamedExceptionBody());
272+
$body->expects(self::once())
273+
->method('close');
274+
275+
$response = $psr17Factory->createResponse(200)
276+
->withHeader('X-ClickHouse-Exception-Tag', 'abcdefghijklmnop')
277+
->withBody($body);
278+
279+
$httpClient = new class ($response) implements ClientInterface {
280+
public function __construct(private ResponseInterface $response)
281+
{
282+
}
283+
284+
public function sendRequest(RequestInterface $request): ResponseInterface
285+
{
286+
return $this->response;
287+
}
288+
};
289+
290+
$client = new PsrClickHouseClient(
291+
$httpClient,
292+
new RequestFactory(
293+
new ParamValueConverterRegistry(),
294+
$psr17Factory,
295+
$psr17Factory,
296+
$psr17Factory,
297+
),
298+
);
299+
300+
$this->expectException(ServerError::class);
301+
302+
$client->select('SELECT throwIf(number = 2) FROM numbers(5)', new TabSeparated());
303+
}
304+
221305
public function testSelectThrowsWhenStreamedExceptionInspectionWouldConsumeNonSeekableBody(): void
222306
{
223307
$psr17Factory = new Psr17Factory();

0 commit comments

Comments
 (0)