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

feat: add Client::insertPayload() #279

Open
wants to merge 1 commit into
base: master
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_AUTH }}"}}'
with:
composer-options: "${{ matrix.composer-options }}"
dependency-versions: "${{ matrix.dependency-versions }}"
Expand Down Expand Up @@ -93,6 +95,8 @@ jobs:

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_AUTH }}"}}'
with:
dependency-versions: "${{ matrix.dependency-versions }}"

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_AUTH }}"}}'

- name: "Run PHP_CodeSniffer"
run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
2 changes: 2 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_AUTH }}"}}'

- name: "Run a static analysis with phpstan/phpstan"
run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"require-dev": {
"cdn77/coding-standard": "^7.0",
"infection/infection": "^0.29.0",
"kafkiansky/phpclick": "dev-bump",
"nyholm/psr7": "^1.2",
"php-http/message-factory": "^1.1",
"phpstan/extension-installer": "^1.1",
Expand All @@ -58,5 +59,9 @@
"psr-4": {
"SimPod\\ClickHouseClient\\Tests\\": "tests/"
}
}
},
"repositories": [{
"type": "vcs",
"url": "https://github.com/simPod/PHPClick"
}]
}
18 changes: 18 additions & 0 deletions src/Client/ClickHouseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace SimPod\ClickHouseClient\Client;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\StreamInterface;
use SimPod\ClickHouseClient\Exception\CannotInsert;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Exception\UnsupportedParamType;
Expand Down Expand Up @@ -85,4 +86,21 @@ public function insert(string $table, array $values, array|null $columns = null,
* @template O of Output
*/
public function insertWithFormat(string $table, Format $inputFormat, string $data, array $settings = []): void;

/**
* @param array<string, float|int|string> $settings
* @param list<string> $columns
* @param Format<Output<mixed>> $inputFormat
*
* @throws ClientExceptionInterface
* @throws CannotInsert
* @throws ServerError
*/
public function insertPayload(
string $table,
Format $inputFormat,
StreamInterface $payload,
array $columns = [],
array $settings = [],
): void;
}
27 changes: 19 additions & 8 deletions src/Client/Http/RequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
UriFactoryInterface|null $uriFactory = null,
UriInterface|string $uri = '',
) {
if ($uriFactory === null && $uri === '') {

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

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "LogicalAnd": @@ @@ /** @throws InvalidArgumentException */ public function __construct(private ParamValueConverterRegistry $paramValueConverterRegistry, private RequestFactoryInterface $requestFactory, private StreamFactoryInterface $streamFactory, UriFactoryInterface|null $uriFactory = null, UriInterface|string $uri = '') { - if ($uriFactory === null && $uri === '') { + if ($uriFactory === null || $uri === '') { $uri = null; } elseif (is_string($uri)) { if ($uriFactory === null) {
$uri = null;
} elseif (is_string($uri)) {
if ($uriFactory === null) {
Expand All @@ -49,11 +49,13 @@
$this->uri = $uri;
}

/** @throws UnsupportedParamType */
public function prepareRequest(RequestOptions $requestOptions): RequestInterface
{
/** @param array<string, mixed> $additionalOptions */
public function initRequest(
RequestSettings $requestSettings,
array $additionalOptions = [],
): RequestInterface {
$query = http_build_query(
$requestOptions->settings,
$requestSettings->settings + $additionalOptions,
'',
'&',
PHP_QUERY_RFC3986,
Expand All @@ -70,11 +72,20 @@
}
}

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

preg_match_all('~\{([a-zA-Z\d]+):([a-zA-Z\d ]+(\(.+\))?)}~', $requestOptions->sql, $matches);
/** @throws UnsupportedParamType */
public function prepareSqlRequest(
string $sql,
RequestSettings $requestSettings,
RequestOptions $requestOptions,
): RequestInterface {
$request = $this->initRequest($requestSettings);

preg_match_all('~\{([a-zA-Z\d]+):([a-zA-Z\d ]+(\(.+\))?)}~', $sql, $matches);
if ($matches[0] === []) {
$body = $this->streamFactory->createStream($requestOptions->sql);
$body = $this->streamFactory->createStream($sql);
try {
return $request->withBody($body);
} catch (InvalidArgumentException) {
Expand All @@ -93,7 +104,7 @@
[],
);

$streamElements = [['name' => 'query', 'contents' => $requestOptions->sql]];
$streamElements = [['name' => 'query', 'contents' => $sql]];
foreach ($requestOptions->params as $name => $value) {
$type = $paramToType[$name] ?? null;
if ($type === null) {
Expand Down
13 changes: 1 addition & 12 deletions src/Client/Http/RequestOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,9 @@

final class RequestOptions
{
/** @var array<string, float|int|string> */
public array $settings;

/**
* @param array<string, mixed> $params
* @param array<string, float|int|string> $defaultSettings
* @param array<string, float|int|string> $querySettings
*/
/** @param array<string, mixed> $params */
public function __construct(
public string $sql,
public array $params,
array $defaultSettings,
array $querySettings,
) {
$this->settings = $querySettings + $defaultSettings;
}
}
22 changes: 22 additions & 0 deletions src/Client/Http/RequestSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Client\Http;

final class RequestSettings
{
/** @var array<string, float|int|string> */
public array $settings;

/**
* @param array<string, float|int|string> $defaultSettings
* @param array<string, float|int|string> $querySettings
*/
public function __construct(
array $defaultSettings,
array $querySettings,
) {
$this->settings = $querySettings + $defaultSettings;
}
}
11 changes: 7 additions & 4 deletions src/Client/PsrClickHouseAsyncClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Psr\Http\Message\ResponseInterface;
use SimPod\ClickHouseClient\Client\Http\RequestFactory;
use SimPod\ClickHouseClient\Client\Http\RequestOptions;
use SimPod\ClickHouseClient\Client\Http\RequestSettings;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Output\Output;
Expand Down Expand Up @@ -83,13 +84,15 @@ private function executeRequest(
array $settings = [],
callable|null $processResponse = null,
): PromiseInterface {
$request = $this->requestFactory->prepareRequest(
new RequestOptions(
$sql,
$params,
$request = $this->requestFactory->prepareSqlRequest(
$sql,
new RequestSettings(
$this->defaultSettings,
$settings,
),
new RequestOptions(
$params,
),
);

return Create::promiseFor(
Expand Down
57 changes: 53 additions & 4 deletions src/Client/PsrClickHouseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
namespace SimPod\ClickHouseClient\Client;

use DateTimeZone;
use InvalidArgumentException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use SimPod\ClickHouseClient\Client\Http\RequestFactory;
use SimPod\ClickHouseClient\Client\Http\RequestOptions;
use SimPod\ClickHouseClient\Client\Http\RequestSettings;
use SimPod\ClickHouseClient\Exception\CannotInsert;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Exception\UnsupportedParamType;
Expand Down Expand Up @@ -59,7 +62,7 @@

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

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

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "MethodCallRemoval": @@ @@ } public function executeQueryWithParams(string $query, array $params, array $settings = []): void { - $this->executeRequest($this->sqlFactory->createWithParameters($query, $params), params: $params, settings: $settings); + } public function select(string $query, Format $outputFormat, array $settings = []): Output {
$this->sqlFactory->createWithParameters($query, $params),
params: $params,
settings: $settings,
Expand All @@ -70,7 +73,7 @@
{
try {
return $this->selectWithParams($query, params: [], outputFormat: $outputFormat, settings: $settings);
} catch (UnsupportedParamValue | UnsupportedParamType) {

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

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "Catch_": @@ @@ { try { return $this->selectWithParams($query, params: [], outputFormat: $outputFormat, settings: $settings); - } catch (UnsupportedParamValue|UnsupportedParamType) { + } catch (UnsupportedParamType) { absurd(); } }
absurd();
}
}
Expand Down Expand Up @@ -198,6 +201,50 @@
}
}

public function insertPayload(
string $table,
Format $inputFormat,
StreamInterface $payload,
array $columns = [],
array $settings = [],
): void {
if ($payload->getSize() === 0) {
throw CannotInsert::noValues();

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

View check run for this annotation

Codecov / codecov/patch

src/Client/PsrClickHouseClient.php#L212

Added line #L212 was not covered by tests
}

$formatSql = $inputFormat::toSql();

$table = Escaper::quoteIdentifier($table);

$columnsSql = $columns === [] ? '' : sprintf('(%s)', implode(',', $columns));

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

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "Ternary": @@ @@ } $formatSql = $inputFormat::toSql(); $table = Escaper::quoteIdentifier($table); - $columnsSql = $columns === [] ? '' : sprintf('(%s)', implode(',', $columns)); + $columnsSql = $columns === [] ? sprintf('(%s)', implode(',', $columns)) : ''; $sql = <<<CLICKHOUSE INSERT INTO {$table} {$columnsSql} {$formatSql} CLICKHOUSE;

$sql = <<<CLICKHOUSE
INSERT INTO $table $columnsSql $formatSql
CLICKHOUSE;

$request = $this->requestFactory->initRequest(
new RequestSettings(
$this->defaultSettings,
$settings,
),
['query' => $sql],
);

try {
$request = $request->withBody($payload);
} catch (InvalidArgumentException) {
absurd();

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

View check run for this annotation

Codecov / codecov/patch

src/Client/PsrClickHouseClient.php#L235-L236

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

$response = $this->client->sendRequest($request);

if ($response->getStatusCode() !== 200) {
throw ServerError::fromResponse($response);

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

View check run for this annotation

Codecov / codecov/patch

src/Client/PsrClickHouseClient.php#L242

Added line #L242 was not covered by tests
}

return;
}

/**
* @param array<string, mixed> $params
* @param array<string, float|int|string> $settings
Expand All @@ -208,13 +255,15 @@
*/
private function executeRequest(string $sql, array $params, array $settings): ResponseInterface
{
$request = $this->requestFactory->prepareRequest(
new RequestOptions(
$sql,
$params,
$request = $this->requestFactory->prepareSqlRequest(
$sql,
new RequestSettings(
$this->defaultSettings,
$settings,
),
new RequestOptions(
$params,
),
);

$response = $this->client->sendRequest($request);
Expand Down
28 changes: 28 additions & 0 deletions src/Format/RowBinary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Format;

use SimPod\ClickHouseClient\Output\Basic;
use SimPod\ClickHouseClient\Output\Output;

/**
* @template T
* @implements Format<Basic<T>>
*/
final class RowBinary implements Format
{
public static function output(string $contents): Output

Check warning on line 16 in src/Format/RowBinary.php

View check run for this annotation

Codecov / codecov/patch

src/Format/RowBinary.php#L16

Added line #L16 was not covered by tests
{
/** @var Basic<T> $output */
$output = new Basic($contents);

Check warning on line 19 in src/Format/RowBinary.php

View check run for this annotation

Codecov / codecov/patch

src/Format/RowBinary.php#L19

Added line #L19 was not covered by tests

return $output;

Check warning on line 21 in src/Format/RowBinary.php

View check run for this annotation

Codecov / codecov/patch

src/Format/RowBinary.php#L21

Added line #L21 was not covered by tests
}

public static function toSql(): string
{
return 'FORMAT RowBinary';
}
}
15 changes: 10 additions & 5 deletions tests/Client/Http/RequestFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPUnit\Framework\Attributes\DataProvider;
use SimPod\ClickHouseClient\Client\Http\RequestFactory;
use SimPod\ClickHouseClient\Client\Http\RequestOptions;
use SimPod\ClickHouseClient\Client\Http\RequestSettings;
use SimPod\ClickHouseClient\Param\ParamValueConverterRegistry;
use SimPod\ClickHouseClient\Tests\TestCaseBase;

Expand All @@ -28,12 +29,16 @@ public function testPrepareRequest(string $uri, string $expectedUri): void
$uri,
);

$request = $requestFactory->prepareRequest(new RequestOptions(
$request = $requestFactory->prepareSqlRequest(
'SELECT 1',
[],
['max_block_size' => 1],
['database' => 'database'],
));
new RequestSettings(
['max_block_size' => 1],
['database' => 'database'],
),
new RequestOptions(
[],
),
);

self::assertSame('POST', $request->getMethod());
self::assertSame(
Expand Down
27 changes: 0 additions & 27 deletions tests/Client/Http/RequestOptionsTest.php

This file was deleted.

Loading
Loading