From cbbc2d73c79d2b363ebe52efbf838bd509674ade Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 29 Jan 2025 11:51:50 +0100 Subject: [PATCH 1/2] feat: support clickhouse timezone input in ParamValueConverterRegistry --- src/Param/ParamValueConverterRegistry.php | 52 +++++++++++++++-------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/Param/ParamValueConverterRegistry.php b/src/Param/ParamValueConverterRegistry.php index 8acebe5..ecf40a5 100644 --- a/src/Param/ParamValueConverterRegistry.php +++ b/src/Param/ParamValueConverterRegistry.php @@ -6,9 +6,11 @@ use Closure; use DateTimeInterface; +use DateTimeZone; use Psr\Http\Message\StreamInterface; use SimPod\ClickHouseClient\Exception\UnsupportedParamType; use SimPod\ClickHouseClient\Sql\Escaper; +use SimPod\ClickHouseClient\Exception\UnsupportedParamValue; use SimPod\ClickHouseClient\Sql\Type; use function array_keys; @@ -18,6 +20,8 @@ use function implode; use function in_array; use function is_array; +use function is_float; +use function is_int; use function is_string; use function json_encode; use function sprintf; @@ -26,13 +30,12 @@ use function trim; /** - * @phpstan-type Converter = Closure(mixed, Type|string|null, bool):(StreamInterface|string) - * @phpstan-type ConverterRegistry = array + * @phpstan-type Converter Closure(mixed, Type|string|null, bool):(StreamInterface|string) + * @phpstan-type ConverterRegistry array */ -final class ParamValueConverterRegistry +final readonly class ParamValueConverterRegistry { - /** @var list */ - private static array $caseInsensitiveTypes = [ + private const CaseInsensitiveTypes = [ 'bool', 'date', 'date32', @@ -90,10 +93,10 @@ public function __construct(array $registry = []) 'bool' => static fn (bool $value) => $value, - 'date' => self::dateConverter(), - 'date32' => self::dateConverter(), - 'datetime' => self::dateTimeConverter(), - 'datetime32' => self::dateTimeConverter(), + 'date' => self::dateConverter($this->clickHouseTimeZone), + 'date32' => self::dateConverter($this->clickHouseTimeZone), + 'datetime' => self::dateTimeConverter($this->clickHouseTimeZone), + 'datetime32' => self::dateTimeConverter($this->clickHouseTimeZone), 'datetime64' => static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface ? $value->format('U.u') : $value, @@ -232,7 +235,7 @@ public function get(Type|string $type): Closure $typeName = strtolower($typeName); $converter = $this->registry[$typeName] ?? null; - if ($converter !== null && in_array($typeName, self::$caseInsensitiveTypes, true)) { + if ($converter !== null && in_array($typeName, self::CaseInsensitiveTypes, true)) { return $converter; } @@ -271,17 +274,32 @@ private static function decimalConverter(): Closure private static function dateConverter(): Closure { - return static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface - // We cannot convert to timestamp yet https://github.com/ClickHouse/ClickHouse/issues/75217 - ? $value->format('Y-m-d') - : $value; + return static function (mixed $value) { + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d'); + } + + if (is_string($value) || is_float($value) || is_int($value)) { + return $value; + } + + throw UnsupportedParamValue::type($value); + }; } private static function dateTimeConverter(): Closure { - return static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface - ? $value->getTimestamp() - : $value; + return static function (mixed $value) { + if ($value instanceof DateTimeInterface) { + return $value->getTimestamp(); + } + + if (is_string($value) || is_float($value) || is_int($value)) { + return $value; + } + + throw UnsupportedParamValue::type($value); + }; } private static function dateIntervalConverter(): Closure From 9ebb3643b46b0bdebda1e6b4b39b2138878c29a3 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 30 Jan 2025 11:57:06 +0100 Subject: [PATCH 2/2] refactor(param-value-converter-registry): wider date converters input type and throw exception --- src/Param/ParamValueConverterRegistry.php | 27 ++++++++++++------- .../Param/ParamValueConverterRegistryTest.php | 8 +++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Param/ParamValueConverterRegistry.php b/src/Param/ParamValueConverterRegistry.php index ecf40a5..190629f 100644 --- a/src/Param/ParamValueConverterRegistry.php +++ b/src/Param/ParamValueConverterRegistry.php @@ -6,11 +6,10 @@ use Closure; use DateTimeInterface; -use DateTimeZone; use Psr\Http\Message\StreamInterface; use SimPod\ClickHouseClient\Exception\UnsupportedParamType; -use SimPod\ClickHouseClient\Sql\Escaper; use SimPod\ClickHouseClient\Exception\UnsupportedParamValue; +use SimPod\ClickHouseClient\Sql\Escaper; use SimPod\ClickHouseClient\Sql\Type; use function array_keys; @@ -33,7 +32,7 @@ * @phpstan-type Converter Closure(mixed, Type|string|null, bool):(StreamInterface|string) * @phpstan-type ConverterRegistry array */ -final readonly class ParamValueConverterRegistry +final class ParamValueConverterRegistry { private const CaseInsensitiveTypes = [ 'bool', @@ -93,13 +92,21 @@ public function __construct(array $registry = []) 'bool' => static fn (bool $value) => $value, - 'date' => self::dateConverter($this->clickHouseTimeZone), - 'date32' => self::dateConverter($this->clickHouseTimeZone), - 'datetime' => self::dateTimeConverter($this->clickHouseTimeZone), - 'datetime32' => self::dateTimeConverter($this->clickHouseTimeZone), - 'datetime64' => static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface - ? $value->format('U.u') - : $value, + 'date' => self::dateConverter(), + 'date32' => self::dateConverter(), + 'datetime' => self::dateTimeConverter(), + 'datetime32' => self::dateTimeConverter(), + 'datetime64' => static function (mixed $value) { + if ($value instanceof DateTimeInterface) { + return $value->format('U.u'); + } + + if (is_string($value) || is_float($value) || is_int($value)) { + return $value; + } + + throw UnsupportedParamValue::type($value); + }, 'Dynamic' => self::noopConverter(), 'Variant' => self::noopConverter(), diff --git a/tests/Param/ParamValueConverterRegistryTest.php b/tests/Param/ParamValueConverterRegistryTest.php index d7a7480..7395ba4 100644 --- a/tests/Param/ParamValueConverterRegistryTest.php +++ b/tests/Param/ParamValueConverterRegistryTest.php @@ -159,7 +159,13 @@ public static function providerConvert(): Generator yield 'Date' => ['Date', '2023-02-01', '2023-02-01']; yield 'Date (datetime)' => ['Date', new DateTimeImmutable('2023-02-01'), '2023-02-01']; yield 'Date32' => ['Date32', new DateTimeImmutable('2023-02-01'), '2023-02-01']; - yield 'DateTime' => ['DateTime', new DateTimeImmutable('2023-02-01 01:02:03'), '2023-02-01 01:02:03']; + yield 'DateTime (string)' => ['DateTime', '2023-02-01 01:02:03', '2023-02-01 01:02:03']; + yield 'DateTime (datetime)' => [ + 'DateTime', + new DateTimeImmutable('2023-02-01 01:02:03'), + '2023-02-01 01:02:03', + ]; + yield 'DateTime32' => ['DateTime32', new DateTimeImmutable('2023-02-01 01:02:03'), '2023-02-01 01:02:03']; yield 'DateTime64(3)' => [ 'DateTime64(3)',