Skip to content

Commit

Permalink
Big 💥
Browse files Browse the repository at this point in the history
  • Loading branch information
simPod committed Jan 20, 2020
1 parent 9868436 commit 99bf04e
Show file tree
Hide file tree
Showing 32 changed files with 969 additions and 179 deletions.
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,50 @@

[![Build Status](https://github.com/simPod/PhpClickHouseClient/workflows/Continuous%20Integration/badge.svg?branch=master)](https://github.com/simPod/PhpClickHouseClient/actions)
[![Coverage Status](https://coveralls.io/repos/github/simPod/PhpClickHouseClient/badge.svg?branch=master)](https://coveralls.io/github/simPod/PhpClickHouseClient?branch=master)
[![Downloads](https://poser.pugx.org/simpod/php-clickhouse-client/d/total.svg)](https://packagist.org/packages/simpod/php-clickhouse-client)
[![Packagist](https://poser.pugx.org/simpod/php-clickhouse-client/v/stable.svg)](https://packagist.org/packages/simpod/php-clickhouse-client)
[![Licence](https://poser.pugx.org/simpod/php-clickhouse-client/license.svg)](https://packagist.org/packages/simpod/php-clickhouse-client)
[![Downloads](https://poser.pugx.org/simpod/clickhouse-client/d/total.svg)](https://packagist.org/packages/simpod/clickhouse-client)
[![Packagist](https://poser.pugx.org/simpod/clickhouse-client/v/stable.svg)](https://packagist.org/packages/simpod/clickhouse-client)
[![Licence](https://poser.pugx.org/simpod/clickhouse-client/license.svg)](https://packagist.org/packages/simpod/clickhouse-client)
[![GitHub Issues](https://img.shields.io/github/issues/simPod/PhpClickHouseClient.svg?style=flat-square)](https://github.com/simPod/PhpClickHouseClient/issues)

PHP Client that talks to ClickHouse via HTTP layer

COMING SOON
## Motivation

The library is trying not to hide any ClickHouse HTTP interface specific details.
That said everything is as much transparent as possible and so object-oriented API is provided without inventing own abstractions.

## Setup

```sh
composer require simpod/clickhouse-client
```

To create a client, create new instance and pass PSR factories:

```php
<?php

use Http\Client\Curl\Client;
use Nyholm\Psr7\Factory\Psr17Factory;
use SimPod\ClickHouseClient\Client\PsrClickHouseClient;
use SimPod\ClickHouseClient\Http\RequestFactory;

$clickHouseClient = new PsrClickHouseClient(
new Client(),
new RequestFactory(
new Psr17Factory,
new Psr17Factory,
new Psr17Factory
),
'localhost:8123',
[
'database' => 'dbname',
'user' => 'username',
'password' => 'secret',
]
);
```

## API

TODO
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
"require": {
"php-64bit": "^7.3",
"guzzlehttp/promises": "^1.3",
"php-ds/php-ds": "^1.2",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
Expand Down
19 changes: 0 additions & 19 deletions src/ClickHouseClient.php

This file was deleted.

20 changes: 20 additions & 0 deletions src/Client/ClickHouseAsyncClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Client;

use GuzzleHttp\Promise\PromiseInterface;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Output\Output;

interface ClickHouseAsyncClient
{
/**
* @see Output hack for IDe to preserve `use`
*
* @phpstan-template O of Output
* @phpstan-param Format<O> $outputFormat
*/
public function select(string $sql, Format $outputFormat) : PromiseInterface;
}
36 changes: 36 additions & 0 deletions src/Client/ClickHouseClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Client;

use Ds\Set;
use Ds\Vector;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Output\Output;

interface ClickHouseClient
{
public function executeQuery(string $sql) : void;

/**
* @param array<string, int|string> $requestParameters
*
* @phpstan-template O of Output
* @phpstan-param Format<O> $outputFormat
* @phpstan-return O
*/
public function select(string $sql, Format $outputFormat, array $requestParameters = []) : Output;

/**
* @param Vector<mixed> $values
* @param Set<string>|null $columns
*/
public function insert(string $table, Vector $values, ?Set $columns = null) : void;

/**
* @phpstan-template O of Output
* @phpstan-param Format<O> $inputFormat
*/
public function insertWithFormat(string $table, Format $inputFormat, string $data) : void;
}
93 changes: 93 additions & 0 deletions src/Client/PsrClickHouseAsyncClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Client;

use GuzzleHttp\Promise\PromiseInterface;
use Http\Client\HttpAsyncClient;
use Psr\Http\Message\ResponseInterface;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Http\RequestFactory;
use SimPod\ClickHouseClient\Http\RequestOptions;
use SimPod\ClickHouseClient\Output\Output;
use function GuzzleHttp\Promise\promise_for;

class PsrClickHouseAsyncClient implements ClickHouseAsyncClient
{
/** @var HttpAsyncClient */
private $asyncClient;

/** @var RequestFactory */
private $requestFactory;

/** @var string */
private $endpoint;

/** @var array<string, int|string> */
private $defaultParameters;

/**
* @param array<string, int|string> $defaultParameters
*/
public function __construct(
HttpAsyncClient $asyncClient,
RequestFactory $requestFactory,
string $endpoint,
array $defaultParameters = []
) {
$this->asyncClient = $asyncClient;
$this->requestFactory = $requestFactory;
$this->endpoint = $endpoint;
$this->defaultParameters = $defaultParameters;
}

public function select(string $sql, Format $outputFormat) : PromiseInterface
{
$formatClause = $outputFormat::toSql();

return $this->executeRequest(
<<<CLICKHOUSE
$sql
$formatClause
CLICKHOUSE,
[],
static function (ResponseInterface $response) use ($outputFormat) : Output {
return $outputFormat::output((string) $response->getBody());
}
);
}

/**
* @param array<string, int|string> $requestParameters
* @param callable(ResponseInterface):mixed|null $processResponse
*/
private function executeRequest(string $sql, array $requestParameters = [], ?callable $processResponse = null) : PromiseInterface
{
$request = $this->requestFactory->prepareRequest(
$this->endpoint,
new RequestOptions(
$sql,
$this->defaultParameters,
$requestParameters
)
);

$promise = promise_for($this->asyncClient->sendAsyncRequest($request));

return $promise->then(
static function (ResponseInterface $response) use ($processResponse) {
if ($response->getStatusCode() !== 200) {
throw ServerError::fromResponse($response);
}

if ($processResponse === null) {
return $response;
}

return $processResponse($response);
}
);
}
}
157 changes: 157 additions & 0 deletions src/Client/PsrClickHouseClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace SimPod\ClickHouseClient\Client;

use Ds\Set;
use Ds\Vector;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use SimPod\ClickHouseClient\Exception\CannotInsert;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Format\Format;
use SimPod\ClickHouseClient\Http\RequestFactory;
use SimPod\ClickHouseClient\Http\RequestOptions;
use SimPod\ClickHouseClient\Output\Output;
use function array_keys;
use function implode;
use function Safe\sprintf;

class PsrClickHouseClient implements ClickHouseClient
{
/** @var ClientInterface */
private $client;

/** @var RequestFactory */
private $requestFactory;

/** @var string */
private $endpoint;

/** @var array<string, int|string> */
private $defaultParameters;

/**
* @param array<string, int|string> $defaultParameters
*/
public function __construct(
ClientInterface $client,
RequestFactory $requestFactory,
string $endpoint,
array $defaultParameters = []
) {
$this->client = $client;
$this->requestFactory = $requestFactory;
$this->endpoint = $endpoint;
$this->defaultParameters = $defaultParameters;
}

public function executeQuery(string $sql) : void
{
$response = $this->executeRequest($sql);

$contents = (string) $response->getBody();

return;
}

public function select(string $sql, Format $outputFormat, array $requestParameters = []) : Output
{
$formatClause = $outputFormat::toSql();

$response = $this->executeRequest(
<<<CLICKHOUSE
$sql
$formatClause
CLICKHOUSE
);

return $outputFormat::output((string) $response->getBody());
}

public function insert(string $table, Vector $values, ?Set $columns = null) : void
{
if ($values->isEmpty()) {
throw CannotInsert::noValues();
}

if ($columns === null) {
/** @var array<mixed> $row */
$row = $values->first();
$columnNames = array_keys($row);
} else {
$columnNames = $columns->toArray();
}

$columnsSql = implode(',', $columnNames);

$valuesSql = implode(
',',
$values->map(
static function (array $map) : string {
return sprintf(
'(%s)',
implode(',', $map)
);
}
)->toArray()
);

$response = $this->executeRequest(
<<<CLICKHOUSE
INSERT INTO $table
($columnsSql)
VALUES $valuesSql
CLICKHOUSE
);
}

public function insertWithFormat(string $table, Format $inputFormat, string $data) : void
{
$formatSql = $inputFormat::toSql();

$this->executeRequest(
<<<CLICKHOUSE
INSERT INTO $table $formatSql $data
CLICKHOUSE
);
}

/**
* @param array<string, int|string> $requestParameters
*/
private function executeRequest(string $sql, array $requestParameters = []) : ResponseInterface
{
$request = $this->requestFactory->prepareRequest(
$this->endpoint,
new RequestOptions(
$sql,
$this->defaultParameters,
$requestParameters
)
);
$response = $this->client->sendRequest($request);
if ($response->getStatusCode() !== 200) {
throw ServerError::fromResponse($response);
}

return $response;
}

/**
* @return array<string, int|string>
*/
public function getDefaultParameters() : array
{
return $this->defaultParameters;
}

/**
* @param array<string, int|string> $defaultParameters
*/
public function setDefaultParameters(array $defaultParameters) : void
{
$this->defaultParameters = $defaultParameters;
}
}
Loading

0 comments on commit 99bf04e

Please sign in to comment.