From 8f39a4024a5f4899b1175431b0493401719fcf80 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 5 Jan 2025 21:23:44 +0100 Subject: [PATCH] Add NumberType --- .github/workflows/static-analysis.yml | 8 +- docs/en/reference/schema-representation.rst | 6 +- docs/en/reference/types.rst | 449 +++++++++--------- src/Types/NumberType.php | 54 +++ src/Types/Type.php | 1 + src/Types/Types.php | 1 + .../Platform/AlterDecimalColumnTest.php | 16 +- tests/Functional/TypeConversionTest.php | 19 + tests/Functional/Types/NumberTest.php | 60 +++ tests/Types/NumberTest.php | 75 +++ 10 files changed, 462 insertions(+), 227 deletions(-) create mode 100644 src/Types/NumberType.php create mode 100644 tests/Functional/Types/NumberTest.php create mode 100644 tests/Types/NumberTest.php diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5fdb47196dc..6c9880f62de 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: php-version: - - "8.3" + - "8.4" steps: - name: "Checkout code" @@ -49,6 +49,8 @@ jobs: - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" + with: + composer-options: "--ignore-platform-req=php+" - name: "Run a static analysis with phpstan/phpstan" run: "vendor/bin/phpstan --error-format=checkstyle | cs2pr" @@ -60,7 +62,7 @@ jobs: strategy: matrix: php-version: - - "8.3" + - "8.4" steps: - name: Checkout code @@ -75,6 +77,8 @@ jobs: - name: Install dependencies with Composer uses: ramsey/composer-install@v3 + with: + composer-options: "--ignore-platform-req=php+" - name: Run static analysis with Vimeo Psalm run: vendor/bin/psalm --shepherd diff --git a/docs/en/reference/schema-representation.rst b/docs/en/reference/schema-representation.rst index 72d4198915c..6fc6e0bba7f 100644 --- a/docs/en/reference/schema-representation.rst +++ b/docs/en/reference/schema-representation.rst @@ -118,11 +118,11 @@ The following options are considered to be fully portable across all database pl in the platform. - **fixed** (boolean): Whether a ``string`` or ``binary`` Doctrine type column has a fixed length. Defaults to ``false``. -- **precision** (integer): The precision of a Doctrine ``decimal`` or ``float`` type - column that determines the overall maximum number of digits to be stored (including scale). +- **precision** (integer): The precision of a Doctrine ``decimal``, ``number`` or ``float`` + type column that determines the overall maximum number of digits to be stored (including scale). Defaults to ``10``. - **scale** (integer): The exact number of decimal digits to be stored in a Doctrine - ``decimal`` or ``float`` type column. Defaults to ``0``. + ``decimal``, ``number`` or ``float`` type column. Defaults to ``0``. - **customSchemaOptions** (array): Additional options for the column that are supported by all vendors: diff --git a/docs/en/reference/types.rst b/docs/en/reference/types.rst index 273a191156c..aa84193b885 100644 --- a/docs/en/reference/types.rst +++ b/docs/en/reference/types.rst @@ -125,6 +125,13 @@ or ``null`` if no data is present. it approximates precision which can lead to false assumptions in applications. +number +++++++ + +Maps and converts numeric data with fixed-point precision. This type behaves like ``decimal``, +but maps to ``BCMath\Number`` value objects instead of strings. Requires PHP 8.4 or newer and +the bcmath extension. + smallfloat ++++++++++ @@ -473,222 +480,232 @@ type is mapped to the database and back to PHP. Please also notice the mapping specific footnotes for additional information. :: - +-------------------+---------------+-----------------------------------------------------------------------------------------------+ - | Doctrine | PHP | Database vendor | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | Name | Version | Type | - +===================+===============+==========================+=========+==========================================================+ - | **smallint** | ``integer`` | **MySQL** | *all* | ``SMALLINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``SMALLINT`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(5)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``INT`` [12] | - | | | | +----------------------------------------------------------+ - | | | | | ``SERIAL`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(10)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **bigint** | ``string`` | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | - | | [8] +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BIGINT`` [12] | - | | | | +----------------------------------------------------------+ - | | | | | ``BIGSERIAL`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``NUMBER(20)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` [11] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``INTEGER`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **decimal** [7] | ``string`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10] | - | | [9] +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **smallfloat** | ``float`` | **MySQL** | *all* | ``FLOAT`` ``UNSIGNED`` [10] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``REAL`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` [10] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``DOUBLE PRECISION`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQL Server** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` [3] | - | [2] [5] | +--------------------------+ | | - | | | **PostgreSQL** | | | - | | +--------------------------+ +----------------------------------------------------------+ - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``VARCHAR2(n)`` [3] | - | | | | +----------------------------------------------------------+ - | | | | | ``CHAR(n)`` [4] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``NVARCHAR(n)`` [3] | - | | | | +----------------------------------------------------------+ - | | | | | ``NCHAR(n)`` [4] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` | - | | | | | ``CHAR(n)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` [16] | - | | | | +----------------------------------------------------------+ - | | | | | ``TEXT`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMTEXT`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGTEXT`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TEXT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``CLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **guid** | ``string`` | **MySQL** | *all* | ``CHAR(36)`` [1] | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` | - | | +--------------------------+ | | - | | | **PostgreSQL** | *all* | ``UUID`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | - | [2] [6] | +--------------------------+ | | - | | | **SQL Server** | +----------------------------------------------------------+ - | | +--------------------------+ | ``BINARY(n)`` [4] | - | | | **Oracle** | *all* | ``RAW(n)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BYTEA`` [15] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQLite** | *all* | ``BLOB`` [15] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **blob** | ``resource`` | **MySQL** | *all* | ``TINYBLOB`` [16] | - | | | | +----------------------------------------------------------+ - | | | | | ``BLOB`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMBLOB`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGBLOB`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``BLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARBINARY(MAX)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BYTEA`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``BOOLEAN`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``BIT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``NUMBER(1)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` | - | | +--------------------------+ | | - | | | **PostgreSQL** | | | - | | +--------------------------+ | | - | | | **Oracle** | | | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+ | - | | | **SQL Server** | "all" | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **datetime** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [13] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``DATETIME`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``TIMESTAMP(0)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **datetimetz** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [14] [15] | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+ | - | | | **SQL Server** | "all" | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` | - | | +--------------------------+ | | - | | | **Oracle** | | | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``DATE`` [15] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | "all" | ``TIME(0)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **simple array** | ``array`` | **MySQL** | *all* | ``TINYTEXT`` [16] | - | [1] | | | +----------------------------------------------------------+ - | | | | | ``TEXT`` [17] | - | | | | +----------------------------------------------------------+ - | | | | | ``MEDIUMTEXT`` [18] | - | | | | +----------------------------------------------------------+ - | | | | | ``LONGTEXT`` [19] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``TEXT`` | - | | +--------------------------+ | | - | | | **Oracle** | *all* | ``CLOB`` | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ - | **json** | ``mixed`` | **MySQL** | *all* | ``JSON`` | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **PostgreSQL** | *all* | ``JSON`` [20] | - | | | | +----------------------------------------------------------+ - | | | | | ``JSONB`` [21] | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **Oracle** | *all* | ``CLOB`` [1] | - | | +--------------------------+ | | - | | | **SQLite** | | | - | | +--------------------------+---------+----------------------------------------------------------+ - | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` [1] | - +-------------------+---------------+--------------------------+---------+----------------------------------------------------------+ + +-------------------+--------------------+-----------------------------------------------------------------------------------------------+ + | Doctrine | PHP | Database vendor | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | Name | Version | Type | + +===================+====================+==========================+=========+==========================================================+ + | **smallint** | ``integer`` | **MySQL** | *all* | ``SMALLINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``SMALLINT`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``NUMBER(5)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQLite** | *all* | ``INTEGER`` [15] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``INT`` [12] | + | | | | +----------------------------------------------------------+ + | | | | | ``SERIAL`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``NUMBER(10)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQLite** | *all* | ``INTEGER`` [15] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **bigint** | ``string`` [8] | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` [10] ``AUTO_INCREMENT`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``BIGINT`` [12] | + | | | | +----------------------------------------------------------+ + | | | | | ``BIGSERIAL`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``NUMBER(20)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` [11] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQLite** | *all* | ``INTEGER`` [15] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **decimal** [7] | ``string`` [9] | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQL Server** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **number** [7] | ``\BCMath\Number`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10] | + | | [9] +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``NUMERIC(p, s)`` | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQL Server** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **smallfloat** | ``float`` | **MySQL** | *all* | ``FLOAT`` ``UNSIGNED`` [10] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``REAL`` | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQL Server** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` [10] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``DOUBLE PRECISION`` | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQL Server** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` [3] | + | [2] [5] | +--------------------------+ | | + | | | **PostgreSQL** | | | + | | +--------------------------+ +----------------------------------------------------------+ + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``VARCHAR2(n)`` [3] | + | | | | +----------------------------------------------------------+ + | | | | | ``CHAR(n)`` [4] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``NVARCHAR(n)`` [3] | + | | | | +----------------------------------------------------------+ + | | | | | ``NCHAR(n)`` [4] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` | + | | | | | ``CHAR(n)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` [16] | + | | | | +----------------------------------------------------------+ + | | | | | ``TEXT`` [17] | + | | | | +----------------------------------------------------------+ + | | | | | ``MEDIUMTEXT`` [18] | + | | | | +----------------------------------------------------------+ + | | | | | ``LONGTEXT`` [19] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``TEXT`` | + | | +--------------------------+ | | + | | | **Oracle** | *all* | ``CLOB`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **guid** | ``string`` | **MySQL** | *all* | ``CHAR(36)`` [1] | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` | + | | +--------------------------+ | | + | | | **PostgreSQL** | *all* | ``UUID`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` [3] | + | [2] [6] | +--------------------------+ | | + | | | **SQL Server** | +----------------------------------------------------------+ + | | +--------------------------+ | ``BINARY(n)`` [4] | + | | | **Oracle** | *all* | ``RAW(n)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``BYTEA`` [15] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQLite** | *all* | ``BLOB`` [15] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **blob** | ``resource`` | **MySQL** | *all* | ``TINYBLOB`` [16] | + | | | | +----------------------------------------------------------+ + | | | | | ``BLOB`` [17] | + | | | | +----------------------------------------------------------+ + | | | | | ``MEDIUMBLOB`` [18] | + | | | | +----------------------------------------------------------+ + | | | | | ``LONGBLOB`` [19] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``BLOB`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``VARBINARY(MAX)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``BYTEA`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``BOOLEAN`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``BIT`` | + | | +--------------------------+ | | + | | | **Oracle** | *all* | ``NUMBER(1)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` | + | | +--------------------------+ | | + | | | **PostgreSQL** | | | + | | +--------------------------+ | | + | | | **Oracle** | | | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+ | + | | | **SQL Server** | "all" | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **datetime** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [13] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``DATETIME`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``TIMESTAMP(0)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **datetimetz** | ``\DateTime`` | **MySQL** | *all* | ``DATETIME`` [14] [15] | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+ | + | | | **SQL Server** | "all" | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` | + | | +--------------------------+ | | + | | | **Oracle** | | | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``DATE`` [15] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | "all" | ``TIME(0)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **simple array** | ``array`` | **MySQL** | *all* | ``TINYTEXT`` [16] | + | [1] | | | +----------------------------------------------------------+ + | | | | | ``TEXT`` [17] | + | | | | +----------------------------------------------------------+ + | | | | | ``MEDIUMTEXT`` [18] | + | | | | +----------------------------------------------------------+ + | | | | | ``LONGTEXT`` [19] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``TEXT`` | + | | +--------------------------+ | | + | | | **Oracle** | *all* | ``CLOB`` | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ + | **json** | ``mixed`` | **MySQL** | *all* | ``JSON`` | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **PostgreSQL** | *all* | ``JSON`` [20] | + | | | | +----------------------------------------------------------+ + | | | | | ``JSONB`` [21] | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **Oracle** | *all* | ``CLOB`` [1] | + | | +--------------------------+ | | + | | | **SQLite** | | | + | | +--------------------------+---------+----------------------------------------------------------+ + | | | **SQL Server** | *all* | ``VARCHAR(MAX)`` [1] | + +-------------------+--------------------+--------------------------+---------+----------------------------------------------------------+ **Notes** diff --git a/src/Types/NumberType.php b/src/Types/NumberType.php new file mode 100644 index 00000000000..3b3d6d118c2 --- /dev/null +++ b/src/Types/NumberType.php @@ -0,0 +1,54 @@ +getDecimalTypeDeclarationSQL($column); + } + + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (! $value instanceof Number) { + throw InvalidType::new($value, static::class, ['null', Number::class]); + } + + return (string) $value; + } + + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Number + { + if ($value === null) { + return null; + } + + // SQLite might return a decimal as float. + if (is_float($value)) { + $value = (string) $value; + } + + try { + return new Number($value); + } catch (TypeError | ValueError $e) { + throw ValueNotConvertible::new($value, static::class, previous: $e); + } + } +} diff --git a/src/Types/Type.php b/src/Types/Type.php index ee7797f0960..f8d218fb753 100644 --- a/src/Types/Type.php +++ b/src/Types/Type.php @@ -34,6 +34,7 @@ abstract class Type Types::DATETIMETZ_MUTABLE => DateTimeTzType::class, Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, Types::DECIMAL => DecimalType::class, + Types::NUMBER => NumberType::class, Types::ENUM => EnumType::class, Types::FLOAT => FloatType::class, Types::GUID => GuidType::class, diff --git a/src/Types/Types.php b/src/Types/Types.php index 319218b021a..0d58bab12e2 100644 --- a/src/Types/Types.php +++ b/src/Types/Types.php @@ -22,6 +22,7 @@ final class Types public const DATETIMETZ_MUTABLE = 'datetimetz'; public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable'; public const DECIMAL = 'decimal'; + public const NUMBER = 'number'; public const FLOAT = 'float'; public const ENUM = 'enum'; public const GUID = 'guid'; diff --git a/tests/Functional/Platform/AlterDecimalColumnTest.php b/tests/Functional/Platform/AlterDecimalColumnTest.php index 2f4583402a3..bc0ddf435e8 100644 --- a/tests/Functional/Platform/AlterDecimalColumnTest.php +++ b/tests/Functional/Platform/AlterDecimalColumnTest.php @@ -9,13 +9,15 @@ use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\Attributes\DataProvider; +use function sprintf; + class AlterDecimalColumnTest extends FunctionalTestCase { #[DataProvider('scaleAndPrecisionProvider')] - public function testAlterPrecisionAndScale(int $newPrecision, int $newScale): void + public function testAlterPrecisionAndScale(int $newPrecision, int $newScale, string $type): void { $table = new Table('decimal_table'); - $column = $table->addColumn('val', Types::DECIMAL, ['precision' => 16, 'scale' => 6]); + $column = $table->addColumn('val', $type, ['precision' => 16, 'scale' => 6]); $this->dropAndCreateTable($table); @@ -36,11 +38,13 @@ public function testAlterPrecisionAndScale(int $newPrecision, int $newScale): vo self::assertSame($newScale, $column->getScale()); } - /** @return iterable */ + /** @return iterable */ public static function scaleAndPrecisionProvider(): iterable { - yield 'Precision' => [12, 6]; - yield 'Scale' => [16, 8]; - yield 'Precision and scale' => [10, 4]; + foreach ([Types::DECIMAL, Types::NUMBER] as $type) { + yield sprintf('Precision (%s)', $type) => [12, 6, $type]; + yield sprintf('Scale (%s)', $type) => [16, 8, $type]; + yield sprintf('Precision and scale (%s)', $type) => [10, 4, $type]; + } } } diff --git a/tests/Functional/TypeConversionTest.php b/tests/Functional/TypeConversionTest.php index 7a577faee92..63bf7507fd9 100644 --- a/tests/Functional/TypeConversionTest.php +++ b/tests/Functional/TypeConversionTest.php @@ -4,6 +4,7 @@ namespace Doctrine\DBAL\Tests\Functional; +use BcMath\Number; use DateTime; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Tests\FunctionalTestCase; @@ -11,6 +12,8 @@ use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use function str_repeat; @@ -38,6 +41,7 @@ protected function setUp(): void $table->addColumn('test_float', Types::FLOAT, ['notnull' => false]); $table->addColumn('test_smallfloat', Types::SMALLFLOAT, ['notnull' => false]); $table->addColumn('test_decimal', Types::DECIMAL, ['notnull' => false, 'scale' => 2, 'precision' => 10]); + $table->addColumn('test_number', Types::NUMBER, ['notnull' => false, 'scale' => 2, 'precision' => 10]); $table->setPrimaryKey(['id']); $this->dropAndCreateTable($table); @@ -154,6 +158,21 @@ public static function toDateTimeProvider(): iterable ]; } + public function testDecimal(): void + { + self::assertSame('13.37', $this->processValue(Types::DECIMAL, '13.37')); + } + + #[RequiresPhp('8.4')] + #[RequiresPhpExtension('bcmath')] + public function testNumber(): void + { + $originalValue = new Number('13.37'); + $dbValue = $this->processValue(Types::NUMBER, $originalValue); + + self::assertSame(0, $originalValue <=> $dbValue); + } + private function processValue(string $type, mixed $originalValue): mixed { $columnName = 'test_' . $type; diff --git a/tests/Functional/Types/NumberTest.php b/tests/Functional/Types/NumberTest.php new file mode 100644 index 00000000000..a57d59af581 --- /dev/null +++ b/tests/Functional/Types/NumberTest.php @@ -0,0 +1,60 @@ +addColumn('val', Types::NUMBER, ['precision' => 4, 'scale' => 2]); + + $this->dropAndCreateTable($table); + + $this->connection->insert( + 'number_table', + ['val' => $expected], + ['val' => Types::NUMBER], + ); + + $value = $this->connection->convertToPHPValue( + $this->connection->fetchOne('SELECT val FROM number_table'), + Types::NUMBER, + ); + + self::assertInstanceOf(Number::class, $value); + self::assertSame(0, $expected <=> $value); + } + + public function testCompareNumberTable(): void + { + $table = new Table('number_table'); + $table->addColumn('val', Types::NUMBER, ['precision' => 4, 'scale' => 2]); + + $this->dropAndCreateTable($table); + + $schemaManager = $this->connection->createSchemaManager(); + + self::assertTrue( + $schemaManager->createComparator() + ->compareTables($schemaManager->introspectTable('number_table'), $table) + ->isEmpty(), + ); + } +} diff --git a/tests/Types/NumberTest.php b/tests/Types/NumberTest.php new file mode 100644 index 00000000000..b69994b87c2 --- /dev/null +++ b/tests/Types/NumberTest.php @@ -0,0 +1,75 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->type = new NumberType(); + } + + #[TestWith(['5.5'])] + #[TestWith(['5.5000'])] + #[TestWith([5.5])] + public function testDecimalConvertsToPHPValue(mixed $dbValue): void + { + $phpValue = $this->type->convertToPHPValue($dbValue, $this->platform); + + self::assertInstanceOf(Number::class, $phpValue); + self::assertSame(0, $phpValue <=> new Number('5.5')); + } + + public function testDecimalNullConvertsToPHPValue(): void + { + self::assertNull($this->type->convertToPHPValue(null, $this->platform)); + } + + public function testNumberConvertsToDecimalString(): void + { + self::assertSame('5.5', $this->type->convertToDatabaseValue(new Number('5.5'), $this->platform)); + } + + public function testNumberNullConvertsToNull(): void + { + self::assertNull($this->type->convertToDatabaseValue(null, $this->platform)); + } + + #[TestWith(['5.5'])] + #[TestWith([new stdClass()])] + public function testInvalidPhpValuesTriggerException(mixed $value): void + { + self::expectException(InvalidType::class); + + $this->type->convertToDatabaseValue($value, $this->platform); + } + + #[TestWith(['foo'])] + #[TestWith([true])] + public function testUnexpectedValuesReturnedByTheDatabaseTriggerException(mixed $value): void + { + self::expectException(ValueNotConvertible::class); + + $this->type->convertToPHPValue($value, $this->platform); + } +}