From 5c080b3b886b9df39b66ca8ceaff4e8217a95780 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 29 Jan 2025 16:18:06 +0100 Subject: [PATCH] Allow passing custom param value converters (#286) Co-authored-by: David --- README.md | 25 +++++++++++++++++++ src/Param/ParamValueConverterRegistry.php | 17 ++++++++----- .../Param/ParamValueConverterRegistryTest.php | 21 ++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8fede91..d34788a 100644 --- a/README.md +++ b/README.md @@ -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 + 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. diff --git a/src/Param/ParamValueConverterRegistry.php b/src/Param/ParamValueConverterRegistry.php index 331d967..70fdcd3 100644 --- a/src/Param/ParamValueConverterRegistry.php +++ b/src/Param/ParamValueConverterRegistry.php @@ -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; @@ -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 + */ final class ParamValueConverterRegistry { /** @var list */ @@ -44,10 +48,11 @@ final class ParamValueConverterRegistry 'json', ]; - /** @phpstan-var array */ + /** @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 @@ -67,8 +72,8 @@ public function __construct() )); }; - /** @phpstan-var array $registry */ - $registry = [ + /** @phpstan-var ConverterRegistry $defaultRegistry */ + $defaultRegistry = [ 'String' => self::stringConverter(), 'FixedString' => self::stringConverter(), @@ -208,7 +213,7 @@ public function __construct() return '(' . $innerExpression . ')'; }, ]; - $this->registry = $registry; + $this->registry = array_merge($defaultRegistry, $registry); } /** diff --git a/tests/Param/ParamValueConverterRegistryTest.php b/tests/Param/ParamValueConverterRegistryTest.php index 1907a59..f02e28e 100644 --- a/tests/Param/ParamValueConverterRegistryTest.php +++ b/tests/Param/ParamValueConverterRegistryTest.php @@ -5,6 +5,8 @@ namespace SimPod\ClickHouseClient\Tests\Param; use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; use Generator; use PHPUnit\Framework\Attributes\BeforeClass; use PHPUnit\Framework\Attributes\CoversClass; @@ -12,6 +14,7 @@ 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; @@ -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, + ), + ); + } }