diff --git a/README.md b/README.md index 1c0afdc84ab987133020c630438bdfadcdba40c0..53721ba0417dbf2c3ed616f3b14717c2496f8034 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,7 @@ As you might have noticed, Tables provide default mapping for most PostgreSQL's - Textual types (`character`, `character varying`, `text`) all map to `string`. - Integer types (`smallint`, `int`, `bigint`) all map to `int`. +- Floating-point types (`real`, `double precision`) all map to `float`. - Boolean type maps to `bool`. - Binary type (`bytea`) maps to a binary `string`. - Json types (`json`, `jsonb`) map to a `json_decode()`'d PHP value. @@ -342,7 +343,7 @@ As you might have noticed, Tables provide default mapping for most PostgreSQL's Additional basic types are only mapped provided that certain packages are installed: - Numeric type (`numeric`/`decimal`) maps to a `BigDecimal` from [brick/math](https://github.com/brick/math). -- Date-time types (`date`, `time`, `timestamp`) map to `LocalDate`, `LocalType`, and `Instant`, respectively, from [brick/date-time](https://github.com/brick/date-time). +- Date-time types (`date`, `time`, `timestamp`) map to `LocalDate`, `LocalTime`, and `Instant`, respectively, from [brick/date-time](https://github.com/brick/date-time). - Uuid type maps to a `Uuid` from [ramsey/uuid](https://github.com/ramsey/uuid). ### Advanced types diff --git a/src/Database/BuiltInType.php b/src/Database/BuiltInType.php index 74b2a7db636b5a05547503783f5bdebfbb334917..271feac4d611cf7d8a45e111abeac9eb3b9ce101 100644 --- a/src/Database/BuiltInType.php +++ b/src/Database/BuiltInType.php @@ -40,6 +40,16 @@ final class BuiltInType implements DatabaseType return new self('numeric'); } + public static function real(): self + { + return new self('real'); + } + + public static function double(): self + { + return new self('double precision'); + } + public static function timestamp(): self { return new self('timestamp without time zone'); diff --git a/src/TypeResolver.php b/src/TypeResolver.php index d8c814899c2eee5525fad6fa166233a15d2fe95d..cc08b8a09358e578ddcc36bd2394ce8912cdfceb 100644 --- a/src/TypeResolver.php +++ b/src/TypeResolver.php @@ -12,6 +12,7 @@ use Grifart\Tables\Types\BinaryType; use Grifart\Tables\Types\BooleanType; use Grifart\Tables\Types\DateType; use Grifart\Tables\Types\DecimalType; +use Grifart\Tables\Types\FloatType; use Grifart\Tables\Types\InstantType; use Grifart\Tables\Types\IntType; use Grifart\Tables\Types\JsonType; @@ -39,6 +40,8 @@ final class TypeResolver $this->addResolutionByTypeName(IntType::smallint()); $this->addResolutionByTypeName(IntType::integer()); $this->addResolutionByTypeName(IntType::bigint()); + $this->addResolutionByTypeName(FloatType::real()); + $this->addResolutionByTypeName(FloatType::double()); $this->addResolutionByTypeName(new BooleanType()); $this->addResolutionByTypeName(new BinaryType()); $this->addResolutionByTypeName(JsonType::json()); diff --git a/src/Types/FloatType.php b/src/Types/FloatType.php new file mode 100644 index 0000000000000000000000000000000000000000..c217b488972d2a46dadb4cfb312e23ccdadded68 --- /dev/null +++ b/src/Types/FloatType.php @@ -0,0 +1,70 @@ +<?php + +declare(strict_types=1); + +namespace Grifart\Tables\Types; + +use Dibi\Expression; +use Grifart\ClassScaffolder\Definition\Types\Type as PhpType; +use Grifart\Tables\Database\BuiltInType; +use Grifart\Tables\Database\DatabaseType; +use Grifart\Tables\Type; +use function Grifart\ClassScaffolder\Definition\Types\resolve; +use function is_infinite; +use function is_nan; +use function sprintf; +use const INF; +use const NAN; + +/** + * @implements Type<float> + */ +final class FloatType implements Type +{ + private function __construct( + private DatabaseType $databaseType, + ) {} + + public static function real(): self + { + return new self(BuiltInType::real()); + } + + public static function double(): self + { + return new self(BuiltInType::double()); + } + + public function getPhpType(): PhpType + { + return resolve('float'); + } + + public function getDatabaseType(): DatabaseType + { + return $this->databaseType; + } + + public function toDatabase(mixed $value): Expression + { + if (is_nan($value)) { + return new Expression('%s', 'NaN'); + } + + if (is_infinite($value)) { + return new Expression('%s', sprintf('%sInfinity', $value < 0 ? '-' : '')); + } + + return new Expression('%f', $value); + } + + public function fromDatabase(mixed $value): float + { + return match ($value) { + 'Infinity' => INF, + '-Infinity' => -INF, + 'NaN' => NAN, + default => (float) $value, + }; + } +} diff --git a/tests/Types/FloatTypeTest.phpt b/tests/Types/FloatTypeTest.phpt new file mode 100644 index 0000000000000000000000000000000000000000..a916b81f54339e33c2a73e44fbe248f7c765e76d --- /dev/null +++ b/tests/Types/FloatTypeTest.phpt @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Grifart\Tables\Tests\Types; + +use Grifart\Tables\Types\FloatType; +use Tester\Assert; +use function Grifart\Tables\Tests\connect; +use function is_nan; + +require __DIR__ . '/../bootstrap.php'; + +$connection = connect(); + +$type = FloatType::double(); + +Assert::same(42.0, $type->fromDatabase(42.0)); +Assert::same(0.5, $type->fromDatabase(0.5)); +Assert::same(INF, $type->fromDatabase('Infinity')); +Assert::same(-INF, $type->fromDatabase('-Infinity')); +Assert::true(is_nan($type->fromDatabase('NaN'))); + +Assert::same('42', $connection->translate($type->toDatabase(42.0))); +Assert::same('0.5', $connection->translate($type->toDatabase(0.5))); +Assert::same("'Infinity'", $connection->translate($type->toDatabase(INF))); +Assert::same("'-Infinity'", $connection->translate($type->toDatabase(-INF))); +Assert::same("'NaN'", $connection->translate($type->toDatabase(NAN)));