Skip to content

Commit

Permalink
feat: pass parameters natively via http interface along the query
Browse files Browse the repository at this point in the history
  • Loading branch information
simPod committed Jan 17, 2024
1 parent 099a346 commit c6a8053
Show file tree
Hide file tree
Showing 23 changed files with 629 additions and 43 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"require": {
"php": "^8.2",
"guzzlehttp/promises": "^2.0",
"guzzlehttp/psr7": "^2.6",
"php-http/client-common": "^2.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
Expand Down
6 changes: 3 additions & 3 deletions src/Client/ClickHouseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Psr\Http\Client\ClientExceptionInterface;
use SimPod\ClickHouseClient\Exception\CannotInsert;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Exception\UnsupportedValue;
use SimPod\ClickHouseClient\Exception\UnsupportedParamValue;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Output\Output;

Expand All @@ -27,7 +27,7 @@ public function executeQuery(string $query, array $settings = []): void;
*
* @throws ClientExceptionInterface
* @throws ServerError
* @throws UnsupportedValue
* @throws UnsupportedParamValue
*/
public function executeQueryWithParams(string $query, array $params, array $settings = []): void;

Expand All @@ -53,7 +53,7 @@ public function select(string $query, Format $outputFormat, array $settings = []
*
* @throws ClientExceptionInterface
* @throws ServerError
* @throws UnsupportedValue
* @throws UnsupportedParamValue
*
* @template O of Output
*/
Expand Down
42 changes: 37 additions & 5 deletions src/Client/Http/RequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

namespace SimPod\ClickHouseClient\Client\Http;

use GuzzleHttp\Psr7\MultipartStream;
use InvalidArgumentException;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use SimPod\ClickHouseClient\Param\ParamValueConverterRegistry;
use SimPod\ClickHouseClient\Sql\Type;

use function array_keys;
use function array_reduce;
use function http_build_query;
use function is_string;
use function preg_match_all;
use function SimPod\ClickHouseClient\absurd;

use const PHP_QUERY_RFC3986;
Expand All @@ -23,8 +28,8 @@ final class RequestFactory

/** @throws InvalidArgumentException */
public function __construct(
private ParamValueConverterRegistry $paramValueConverterRegistry,
private RequestFactoryInterface $requestFactory,
private StreamFactoryInterface $streamFactory,
UriFactoryInterface|null $uriFactory = null,
UriInterface|string $uri = '',
) {
Expand All @@ -50,8 +55,6 @@ public function prepareRequest(RequestOptions $requestOptions): RequestInterface
PHP_QUERY_RFC3986,
);

$body = $this->streamFactory->createStream($requestOptions->sql);

if ($this->uri === null) {
$uri = $query === '' ? '' : '?' . $query;
} else {
Expand All @@ -64,8 +67,37 @@ public function prepareRequest(RequestOptions $requestOptions): RequestInterface
}

$request = $this->requestFactory->createRequest('POST', $uri);

preg_match_all('~\{([a-zA-Z\d]+):([a-zA-Z\d ]+(\(.+\))?)}~', $requestOptions->sql, $matches);

$typeToParam = array_reduce(
array_keys($matches[1]),
static function (array $acc, string|int $k) use ($matches) {
$acc[$matches[1][$k]] = Type::fromString($matches[2][$k]);

return $acc;
},
[],
);

$streamElements = [['name' => 'query', 'contents' => $requestOptions->sql]];
foreach ($requestOptions->params as $name => $value) {
$type = $typeToParam[$name] ?? null;
if ($type === null) {
continue;

Check warning on line 87 in src/Client/Http/RequestFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Client/Http/RequestFactory.php#L87

Added line #L87 was not covered by tests
}

$streamElements[] = [
'name' => 'param_' . $name,
'contents' => $this->paramValueConverterRegistry->get($type)($value, $type, false),
];
}

try {
$request = $request->withBody($body);
$body = new MultipartStream($streamElements);
$request = $request
->withHeader('Content-Type', 'multipart/form-data; boundary=' . $body->getBoundary())
->withBody($body);
} catch (InvalidArgumentException) {
absurd();
}
Expand Down
9 changes: 7 additions & 2 deletions src/Client/Http/RequestOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ final class RequestOptions
public array $settings;

/**
* @param array<string, mixed> $params
* @param array<string, float|int|string> $defaultSettings
* @param array<string, float|int|string> $querySettings
*/
public function __construct(public string $sql, array $defaultSettings, array $querySettings)
{
public function __construct(
public string $sql,
public array $params,
array $defaultSettings,
array $querySettings,
) {
$this->settings = $querySettings + $defaultSettings;
}
}
10 changes: 8 additions & 2 deletions src/Client/PsrClickHouseAsyncClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,31 @@ public function selectWithParams(
$sql
$formatClause
CLICKHOUSE,
$settings,
static fn (ResponseInterface $response): Output => $outputFormat::output($response->getBody()->__toString())
params: $params,
settings: $settings,
processResponse: static fn (ResponseInterface $response): Output => $outputFormat::output(
$response->getBody()->__toString(),
)
);
}

/**
* @param array<string, mixed> $params
* @param array<string, float|int|string> $settings
* @param (callable(ResponseInterface):mixed)|null $processResponse
*
* @throws Exception
*/
private function executeRequest(
string $sql,
array $params,
array $settings = [],
callable|null $processResponse = null,
): PromiseInterface {
$request = $this->requestFactory->prepareRequest(
new RequestOptions(
$sql,
$params,
$this->defaultSettings,
$settings,
),
Expand Down
14 changes: 10 additions & 4 deletions src/Client/PsrClickHouseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use SimPod\ClickHouseClient\Client\Http\RequestOptions;
use SimPod\ClickHouseClient\Exception\CannotInsert;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Exception\UnsupportedValue;
use SimPod\ClickHouseClient\Exception\UnsupportedParamValue;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Output\Output;
use SimPod\ClickHouseClient\Sql\Escaper;
Expand Down Expand Up @@ -46,13 +46,14 @@ public function __construct(

public function executeQuery(string $query, array $settings = []): void
{
$this->executeRequest($query, settings: $settings);
$this->executeRequest($query, params: [], settings: $settings);
}

public function executeQueryWithParams(string $query, array $params, array $settings = []): void
{
$this->executeRequest(
$this->sqlFactory->createWithParameters($query, $params),
params: $params,

Check warning on line 56 in src/Client/PsrClickHouseClient.php

View check run for this annotation

Codecov / codecov/patch

src/Client/PsrClickHouseClient.php#L56

Added line #L56 was not covered by tests
settings: $settings,
);
}
Expand All @@ -61,7 +62,7 @@ public function select(string $query, Format $outputFormat, array $settings = []
{
try {
return $this->selectWithParams($query, params: [], outputFormat: $outputFormat, settings: $settings);
} catch (UnsupportedValue) {
} catch (UnsupportedParamValue) {
absurd();
}
}
Expand All @@ -77,6 +78,7 @@ public function selectWithParams(string $query, array $params, Format $outputFor
$sql
$formatClause
CLICKHOUSE,
params: $params,
settings: $settings,
);

Expand Down Expand Up @@ -118,6 +120,7 @@ public function insert(string $table, array $values, array|null $columns = null,
$columnsSql
VALUES $valuesSql
CLICKHOUSE,
params: [],
settings: $settings,
);
}
Expand All @@ -132,21 +135,24 @@ public function insertWithFormat(string $table, Format $inputFormat, string $dat
<<<CLICKHOUSE
INSERT INTO $table $formatSql $data
CLICKHOUSE,
params: [],
settings: $settings,
);
}

/**
* @param array<string, mixed> $params
* @param array<string, float|int|string> $settings
*
* @throws ServerError
* @throws ClientExceptionInterface
*/
private function executeRequest(string $sql, array $settings): ResponseInterface
private function executeRequest(string $sql, array $params, array $settings): ResponseInterface
{
$request = $this->requestFactory->prepareRequest(
new RequestOptions(
$sql,
$params,
$this->defaultSettings,
$settings,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use function sprintf;
use function var_export;

final class UnsupportedValue extends InvalidArgumentException implements ClickHouseClientException
final class UnsupportedParamValue extends InvalidArgumentException implements ClickHouseClientException
{
public static function type(mixed $value): self
{
Expand Down
16 changes: 16 additions & 0 deletions src/Exception/UnsupportedType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Exception;

use InvalidArgumentException;
use SimPod\ClickHouseClient\Sql\Type;

final class UnsupportedType extends InvalidArgumentException implements ClickHouseClientException
{
public static function fromType(Type $type): self

Check warning on line 12 in src/Exception/UnsupportedType.php

View check run for this annotation

Codecov / codecov/patch

src/Exception/UnsupportedType.php#L12

Added line #L12 was not covered by tests
{
return new self($type->name);

Check warning on line 14 in src/Exception/UnsupportedType.php

View check run for this annotation

Codecov / codecov/patch

src/Exception/UnsupportedType.php#L14

Added line #L14 was not covered by tests
}
}
Loading

0 comments on commit c6a8053

Please sign in to comment.