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: allow passing custom param value converters #291

Closed
wants to merge 1 commit into from
Closed
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ $output = $client->selectWithParams(
All types are supported (except `AggregateFunction`, `SimpleAggregateFunction` and `Nothing` by design).
You can also pass `DateTimeInterface` into `Date*` types or native array into `Array`, `Tuple`, `Native` and `Geo` types

### Custom Query Parameter Value Conversion

Query parameters passed to `selectWithParams()` are converted into an HTTP-API-compatible format. To overwrite an existing value converter or
provide a converter for a type that the library does not (yet) support, pass these to the
`SimPod\ClickHouseClient\Param\ParamValueConverterRegistry` constructor:

```php
<?php

use SimPod\ClickHouseClient\Client\Http\RequestFactory;
use SimPod\ClickHouseClient\Client\PsrClickHouseClient;
use SimPod\ClickHouseClient\Exception\UnsupportedParamValue;
use SimPod\ClickHouseClient\Param\ParamValueConverterRegistry;

$paramValueConverterRegistry = new ParamValueConverterRegistry([
'datetime' => static fn (mixed $v) => $v instanceof DateTimeInterface ? $v->format('c') : throw UnsupportedParamValue::type($value)
]);

$client = new PsrClickHouseClient(..., new RequestFactory($paramValueConverterRegistry, ...));
```

Be aware that the library can not ensure that passed values have a certain type. They are passed as-is and closures must accept `mixed` values.

Throw an exception of type `UnsupportedParamValue` if your converter does not support the passed value type.

### Expression

To represent complex expressions there's `SimPod\ClickHouseClient\Sql\Expression` class. When passed to `SqlFactory` its value gets evaluated.
Expand Down
17 changes: 11 additions & 6 deletions src/Param/ParamValueConverterRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

use function array_keys;
use function array_map;
use function array_merge;
use function explode;
use function implode;
use function in_array;
Expand All @@ -24,7 +25,10 @@
use function strtolower;
use function trim;

/** @phpstan-type Converter = Closure(mixed, Type|string|null, bool):(StreamInterface|string) */
/**
* @phpstan-type Converter = Closure(mixed, Type|string|null, bool):(StreamInterface|string)
* @phpstan-type ConverterRegistry = array<string, Converter>
*/
final class ParamValueConverterRegistry
{
/** @var list<string> */
Expand All @@ -44,10 +48,11 @@ final class ParamValueConverterRegistry
'json',
];

/** @phpstan-var array<string, Converter> */
/** @phpstan-var ConverterRegistry */
private array $registry;

public function __construct()
/** @phpstan-param ConverterRegistry $registry */
public function __construct(array $registry = [])
{
$formatPoint = static fn (array $point) => sprintf('(%s)', implode(',', $point));
// phpcs:ignore SlevomatCodingStandard.Functions.RequireArrowFunction.RequiredArrowFunction
Expand All @@ -67,8 +72,8 @@ public function __construct()
));
};

/** @phpstan-var array<string, Converter> $registry */
$registry = [
/** @phpstan-var ConverterRegistry $defaultRegistry */
$defaultRegistry = [
'String' => self::stringConverter(),
'FixedString' => self::stringConverter(),

Expand Down Expand Up @@ -208,7 +213,7 @@ public function __construct()
return '(' . $innerExpression . ')';
},
];
$this->registry = $registry;
$this->registry = array_merge($defaultRegistry, $registry);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions tests/Param/ParamValueConverterRegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
namespace SimPod\ClickHouseClient\Tests\Param;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Generator;
use PHPUnit\Framework\Attributes\BeforeClass;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Psr\Http\Client\ClientExceptionInterface;
use SimPod\ClickHouseClient\Exception\ServerError;
use SimPod\ClickHouseClient\Exception\UnsupportedParamType;
use SimPod\ClickHouseClient\Exception\UnsupportedParamValue;
use SimPod\ClickHouseClient\Format\JsonEachRow;
use SimPod\ClickHouseClient\Format\TabSeparated;
use SimPod\ClickHouseClient\Param\ParamValueConverterRegistry;
Expand Down Expand Up @@ -294,4 +297,22 @@ public function testThrowsOnUnknownType(): void
$this->expectException(UnsupportedParamType::class);
$registry->get('fOo');
}

public function testParameterRegistryOverwrite(): void
{
$registry = new ParamValueConverterRegistry([
'datetime' => static fn (mixed $value) => $value instanceof DateTimeInterface
? $value->format('c')
: throw UnsupportedParamValue::value($value),
]);

self::assertSame(
'2025-01-28T16:00:00+01:00',
$registry->get('datetime')(
new DateTimeImmutable('2025-01-28T16:00:00', new DateTimeZone('+0100')),
null,
false,
),
);
}
}
Loading