diff --git a/src/Type/Doctrine/Descriptors/FloatType.php b/src/Type/Doctrine/Descriptors/FloatType.php index f435293b..b5d6c19f 100644 --- a/src/Type/Doctrine/Descriptors/FloatType.php +++ b/src/Type/Doctrine/Descriptors/FloatType.php @@ -52,18 +52,12 @@ public function getDatabaseInternalTypeForDriver(Connection $connection): Type { $driverType = $this->driverDetector->detect($connection); - if ($driverType === DriverDetector::PDO_PGSQL) { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - } - if (in_array($driverType, [ DriverDetector::SQLITE3, DriverDetector::PDO_SQLITE, DriverDetector::MYSQLI, DriverDetector::PDO_MYSQL, + DriverDetector::PDO_PGSQL, DriverDetector::PGSQL, ], true)) { return new \PHPStan\Type\FloatType(); diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 9e5c475f..4b96044d 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -463,10 +463,6 @@ public function walkFunction($function): string } if ($this->containsOnlyNumericTypes($exprTypeNoNull)) { - if ($this->driverType === DriverDetector::PDO_PGSQL) { - return $this->marshalType($this->createNumericString($nullable)); - } - return $this->marshalType($exprType); // retains underlying type } @@ -619,13 +615,7 @@ public function walkFunction($function): string $type = TypeCombinator::addNull($type); } - } elseif ($this->driverType === DriverDetector::PDO_PGSQL) { - $type = new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); - - } elseif ($this->driverType === DriverDetector::PGSQL) { + } elseif ($this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { $castedExprType = $this->castStringLiteralForNumericExpression($exprTypeNoNull); if ($castedExprType->isInteger()->yes() || $castedExprType->isFloat()->yes()) { @@ -1763,12 +1753,6 @@ private function inferPlusMinusTimesType(array $termTypes): Type return $this->createInteger($nullable); } - if ($this->driverType === DriverDetector::PDO_PGSQL) { - if ($this->containsOnlyNumericTypes($unionWithoutNull)) { - return $this->createNumericString($nullable); - } - } - if ($this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { if (!$this->containsOnlyNumericTypes(...$typesNoNull)) { return new MixedType(); @@ -1783,7 +1767,7 @@ private function inferPlusMinusTimesType(array $termTypes): Type return $this->createFloatOrInt($nullable); } - if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL) { + if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType()])) { return $this->createFloat($nullable); } @@ -1849,12 +1833,6 @@ private function inferDivisionType(array $termTypes): Type return new MixedType(); } - if ($this->driverType === DriverDetector::PDO_PGSQL) { - if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); - } - } - if ($this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { if (!$this->containsOnlyNumericTypes(...$typesNoNull)) { return new MixedType(); @@ -1869,7 +1847,7 @@ private function inferDivisionType(array $termTypes): Type return $this->createFloatOrInt($nullable); } - if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL) { + if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), new FloatType()])) { return $this->createFloat($nullable); } @@ -2090,6 +2068,9 @@ private function hasAggregateWithoutGroupBy(): bool * - pdo_sqlite: https://github.com/php/php-src/commit/438b025a28cda2935613af412fc13702883dd3a2 * - pdo_pgsql: https://github.com/php/php-src/commit/737195c3ae6ac53b9501cfc39cc80fd462909c82 * + * Notable 8.4 changes: + * - pdo_pgsql: https://github.com/php/php-src/commit/6d10a6989897e9089d62edf939344437128e93ad + * * @param IntegerType|FloatType|BooleanType $type */ private function shouldStringifyExpressions(Type $type): TrinaryLogic @@ -2134,7 +2115,14 @@ private function shouldStringifyExpressions(Type $type): TrinaryLogic } return TrinaryLogic::createNo(); + } + if ($type->isFloat()->yes()) { + if ($this->phpVersion->getVersionId() >= 80400) { + return TrinaryLogic::createFromBoolean($stringifyFetches); + } + + return TrinaryLogic::createYes(); } return TrinaryLogic::createFromBoolean($stringifyFetches); diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 97df9e20..d5f0a9f2 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -71,6 +71,7 @@ final class QueryResultTypeWalkerFetchTypeMatrixTest extends PHPStanTestCase private const STRINGIFY_NONE = 'none'; private const STRINGIFY_DEFAULT = 'default'; private const STRINGIFY_PG_BOOL = 'pg_bool'; + private const STRINGIFY_PG_FLOAT = 'pg_float'; private const CONFIG_DEFAULT = 'default'; private const CONFIG_STRINGIFY = 'pdo_stringify'; @@ -974,15 +975,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 9.125, 'sqliteResult' => 9.125, - 'pdoPgsqlResult' => '9.125', + 'pdoPgsqlResult' => 9.125, 'pgsqlResult' => 9.125, 'mssqlResult' => 9.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int + t.col_mixed' => [ @@ -1006,15 +1007,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2147483648.125, 'sqliteResult' => 2147483648.125, - 'pdoPgsqlResult' => '2147483648.125', + 'pdoPgsqlResult' => 2147483648.125, 'pgsqlResult' => 2147483648.125, 'mssqlResult' => 2147483648.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_bigint + t.col_float (int data)' => [ @@ -1022,15 +1023,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2.0, 'sqliteResult' => 2.0, - 'pdoPgsqlResult' => '2', + 'pdoPgsqlResult' => 2.0, 'pgsqlResult' => 2.0, 'mssqlResult' => 2.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float + t.col_float' => [ @@ -1038,15 +1039,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.25, 'sqliteResult' => 0.25, - 'pdoPgsqlResult' => '0.25', + 'pdoPgsqlResult' => 0.25, 'pgsqlResult' => 0.25, 'mssqlResult' => 0.25, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int + t.col_decimal' => [ @@ -1086,15 +1087,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.225, 'sqliteResult' => 0.225, - 'pdoPgsqlResult' => '0.225', + 'pdoPgsqlResult' => 0.225, 'pgsqlResult' => 0.225, 'mssqlResult' => 0.225, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float + t.col_decimal (int data)' => [ @@ -1102,15 +1103,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 2.0, 'sqliteResult' => 2.0, - 'pdoPgsqlResult' => '2', + 'pdoPgsqlResult' => 2.0, 'pgsqlResult' => 2.0, 'mssqlResult' => 2.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal + t.col_decimal (int data)' => [ @@ -1134,15 +1135,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int + t.col_float + t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 9.225, 'sqliteResult' => 9.225, - 'pdoPgsqlResult' => '9.225', + 'pdoPgsqlResult' => 9.225, 'pgsqlResult' => 9.225, 'mssqlResult' => 9.225, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal + t.col_decimal' => [ @@ -1310,15 +1311,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 72.0, 'sqliteResult' => 72.0, - 'pdoPgsqlResult' => '72', + 'pdoPgsqlResult' => 72.0, 'pgsqlResult' => 72.0, 'mssqlResult' => 72.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int / t.col_float / t.col_decimal' => [ @@ -1326,15 +1327,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_int / t.col_float / t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 720.0, 'sqliteResult' => 720.0, - 'pdoPgsqlResult' => '720', + 'pdoPgsqlResult' => 720.0, 'pgsqlResult' => 720.0, 'mssqlResult' => 720.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_bigint / t.col_float' => [ @@ -1342,15 +1343,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_bigint / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 17179869184.0, 'sqliteResult' => 17179869184.0, - 'pdoPgsqlResult' => '17179869184', + 'pdoPgsqlResult' => 17179869184.0, 'pgsqlResult' => 17179869184.0, 'mssqlResult' => 17179869184.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_float / t.col_float' => [ @@ -1358,15 +1359,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float / t.col_float FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_int / t.col_decimal' => [ @@ -1406,15 +1407,15 @@ public static function provideCases(): iterable 'select' => 'SELECT t.col_float / t.col_decimal FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.25, 'sqliteResult' => 1.25, - 'pdoPgsqlResult' => '1.25', + 'pdoPgsqlResult' => 1.25, 'pgsqlResult' => 1.25, 'mssqlResult' => 1.25, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal / t.col_decimal' => [ @@ -1961,20 +1962,36 @@ public static function provideCases(): iterable 'stringify' => self::STRINGIFY_DEFAULT, ]; + yield 'COALESCE(t.col_float, t.col_float)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(t.col_float, t.col_float) FROM %s t', + 'mysql' => self::float(), + 'sqlite' => self::float(), + 'pdo_pgsql' => self::float(), + 'pgsql' => self::float(), + 'mssql' => self::mixed(), + 'mysqlResult' => 0.125, + 'sqliteResult' => 0.125, + 'pdoPgsqlResult' => 0.125, + 'pgsqlResult' => 0.125, + 'mssqlResult' => 0.125, + 'stringify' => self::STRINGIFY_PG_FLOAT, + ]; + yield 'COALESCE(t.col_float, t.col_float) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT COALESCE(t.col_float, t.col_float) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 't.col_decimal' => [ @@ -2046,15 +2063,15 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float) + no data' => [ @@ -2062,7 +2079,7 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2070,7 +2087,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float) + GROUP BY' => [ @@ -2078,15 +2095,15 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_float_nullable) + GROUP BY' => [ @@ -2094,7 +2111,7 @@ public static function provideCases(): iterable 'select' => 'SELECT AVG(t.col_float_nullable) FROM %s t GROUP BY t.col_int', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2102,7 +2119,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'AVG(t.col_decimal)' => [ @@ -2350,15 +2367,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_float) + no data' => [ @@ -2366,7 +2383,7 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2374,7 +2391,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_float) + GROUP BY' => [ @@ -2382,15 +2399,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield '1 + -(CASE WHEN MIN(t.col_float) = 0 THEN SUM(t.col_float) ELSE 0 END)' => [ // agg function (causing null) deeply inside AST @@ -2398,15 +2415,15 @@ public static function provideCases(): iterable 'select' => 'SELECT 1 + -(CASE WHEN MIN(t.col_float) = 0 THEN SUM(t.col_float) ELSE 0 END) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SUM(t.col_decimal)' => [ @@ -2686,15 +2703,15 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_float) + no data' => [ @@ -2702,7 +2719,7 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -2710,7 +2727,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_float) + GROUP BY' => [ @@ -2718,15 +2735,15 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(t.col_float) FROM %s t GROUP BY t.col_int', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'MAX(t.col_decimal)' => [ @@ -2958,15 +2975,15 @@ public static function provideCases(): iterable 'select' => 'SELECT ABS(t.col_float) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 0.125, 'sqliteResult' => 0.125, - 'pdoPgsqlResult' => '0.125', + 'pdoPgsqlResult' => 0.125, 'pgsqlResult' => 0.125, 'mssqlResult' => 0.125, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'ABS(t.col_decimal)' => [ @@ -3166,15 +3183,15 @@ public static function provideCases(): iterable 'select' => "SELECT ABS('1.0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "ABS('1')" => [ @@ -3182,15 +3199,15 @@ public static function provideCases(): iterable 'select' => "SELECT ABS('1') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'ABS(t.col_bigint)' => [ @@ -3614,15 +3631,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_float) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_decimal)' => [ @@ -3646,15 +3663,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_int) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 3.0, 'sqliteResult' => 3.0, - 'pdoPgsqlResult' => '3', + 'pdoPgsqlResult' => 3.0, 'pgsqlResult' => 3.0, 'mssqlResult' => 3.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_mixed)' => [ @@ -3667,10 +3684,10 @@ public static function provideCases(): iterable 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(t.col_int_nullable)' => [ @@ -3678,7 +3695,7 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_int_nullable) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => PHP_VERSION_ID >= 80100 && !self::hasDbal4() ? null : self::floatOrNull(), // fails in UDF since PHP 8.1: sqrt(): Passing null to parameter #1 ($num) of type float is deprecated - 'pdo_pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::floatOrNull(), 'pgsql' => self::floatOrNull(), 'mssql' => self::mixed(), 'mysqlResult' => null, @@ -3686,7 +3703,7 @@ public static function provideCases(): iterable 'pdoPgsqlResult' => null, 'pgsqlResult' => null, 'mssqlResult' => null, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'SQRT(-1)' => [ @@ -3710,15 +3727,15 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(1) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1')" => [ @@ -3726,15 +3743,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1.0')" => [ @@ -3742,15 +3759,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1.0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('1e0')" => [ @@ -3758,15 +3775,15 @@ public static function provideCases(): iterable 'select' => "SELECT SQRT('1e0') FROM %s t", 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => self::float(), 'pgsql' => self::float(), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield "SQRT('foo')" => [ @@ -4153,6 +4170,38 @@ public static function provideCases(): iterable 'stringify' => self::STRINGIFY_DEFAULT, ]; + yield 'COALESCE(0, 0)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(0, 0) FROM %s t', + 'mysql' => self::int(), + 'sqlite' => self::int(), + 'pdo_pgsql' => self::int(), + 'pgsql' => self::int(), + 'mssql' => self::mixed(), + 'mysqlResult' => 0, + 'sqliteResult' => 0, + 'pdoPgsqlResult' => 0, + 'pgsqlResult' => 0, + 'mssqlResult' => 0, + 'stringify' => self::STRINGIFY_DEFAULT, + ]; + + yield 'COALESCE(1.0, 1.0)' => [ + 'data' => self::dataDefault(), + 'select' => 'SELECT COALESCE(1.0, 1.0) FROM %s t', + 'mysql' => self::numericString(), + 'sqlite' => self::float(), + 'pdo_pgsql' => self::numericString(), + 'pgsql' => self::numericString(), + 'mssql' => self::mixed(), + 'mysqlResult' => '1.0', + 'sqliteResult' => 1.0, + 'pdoPgsqlResult' => '1.0', + 'pgsqlResult' => '1.0', + 'mssqlResult' => '1.0', + 'stringify' => self::STRINGIFY_DEFAULT, + ]; + yield 'COALESCE(1e0, 1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(1e0, 1.0) FROM %s t', @@ -4238,12 +4287,14 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::float(), self::int()), 'pgsql' => TypeCombinator::union(self::float(), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, 'stringify' => self::STRINGIFY_DEFAULT, @@ -4254,15 +4305,15 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), + 'pdo_pgsql' => TypeCombinator::union(self::float(), self::numericString()), 'pgsql' => TypeCombinator::union(self::float(), self::numericString()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_int_nullable, t.col_decimal_nullable, 0)' => [ @@ -4286,12 +4337,14 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, 'stringify' => self::STRINGIFY_DEFAULT, @@ -4302,12 +4355,14 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, 'stringify' => self::STRINGIFY_DEFAULT, @@ -4318,12 +4373,14 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, 0e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, 'stringify' => self::STRINGIFY_DEFAULT, @@ -4334,12 +4391,14 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(t.col_int_nullable, t.col_decimal_nullable, t.col_float_nullable, \'0\') FROM %s t', 'mysql' => self::numericString(), 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => PHP_VERSION_ID < 80400 + ? TypeCombinator::union(self::numericString(), self::int()) + : TypeCombinator::union(self::numericString(), self::int(), self::float()), 'pgsql' => TypeCombinator::union(self::numericString(), self::int(), self::float()), 'mssql' => self::mixed(), 'mysqlResult' => '0', 'sqliteResult' => '0', - 'pdoPgsqlResult' => '0', + 'pdoPgsqlResult' => PHP_VERSION_ID < 80400 ? '0' : 0.0, 'pgsqlResult' => 0.0, 'mssqlResult' => 0.0, 'stringify' => self::STRINGIFY_DEFAULT, @@ -4371,10 +4430,10 @@ public static function provideCases(): iterable 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, - 'pdoPgsqlResult' => '1', + 'pdoPgsqlResult' => 1.0, 'pgsqlResult' => 1.0, 'mssqlResult' => 1.0, - 'stringify' => self::STRINGIFY_DEFAULT, + 'stringify' => self::STRINGIFY_PG_FLOAT, ]; yield 'COALESCE(t.col_string_nullable, t.col_int)' => [ @@ -4996,6 +5055,15 @@ private function resolveDefaultBooleanStringification(?string $driver, int $php, return $this->resolveDefaultStringification($driver, $php, $configName); } + private function resolveDefaultFloatStringification(?string $driver, int $php, string $configName): bool + { + if ($php < 80400 && $driver === DriverDetector::PDO_PGSQL) { + return true; // pdo_pgsql does stringify floats even without ATTR_STRINGIFY_FETCHES prior to PHP 8.4 + } + + return $this->resolveDefaultStringification($driver, $php, $configName); + } + private function getHumanReadablePhpVersion(int $phpVersion): string { return floor($phpVersion / 10000) . '.' . floor(($phpVersion % 10000) / 100); @@ -5024,6 +5092,10 @@ private function shouldStringify(string $stringification, ?string $driverType, i return $this->resolveDefaultBooleanStringification($driverType, $phpVersion, $configName); } + if ($stringification === self::STRINGIFY_PG_FLOAT) { + return $this->resolveDefaultFloatStringification($driverType, $phpVersion, $configName); + } + throw new LogicException('Unknown stringification: ' . $stringification); } diff --git a/tests/Platform/README.md b/tests/Platform/README.md index d3678117..06d8b843 100644 --- a/tests/Platform/README.md +++ b/tests/Platform/README.md @@ -8,18 +8,22 @@ Set current working directory to project root. - `printf "UID=$(id -u)\nGID=$(id -g)" > .env` - `docker-compose -f tests/Platform/docker/docker-compose.yml up -d` -# Test behaviour with old stringification +# Test behaviour for PHP 8.0 (old stringification) - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php80 composer update` - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php80 php -d memory_limit=1G vendor/bin/phpunit --group=platform` -# Test behaviour with new stringification +# Test behaviour for PHP 8.1 (adjusted stringification) - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 composer update` - `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 php -d memory_limit=1G vendor/bin/phpunit --group=platform` + +# Test behaviour for PHP 8.4 (pdo_pgsql float stringification fix) +- `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 composer update` +- `docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 php -d memory_limit=1G vendor/bin/phpunit --group=platform` ``` You can also run utilize those containers for PHPStorm PHPUnit configuration. Since the dataset is huge and takes few minutes to run, you can filter only functions you are interested in: ```sh -docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php81 php -d memory_limit=1G vendor/bin/phpunit --group=platform --filter "AVG" +docker-compose -f tests/Platform/docker/docker-compose.yml run --rm php84 php -d memory_limit=1G vendor/bin/phpunit --group=platform --filter "AVG" ``` diff --git a/tests/Platform/docker/Dockerfile84 b/tests/Platform/docker/Dockerfile84 new file mode 100644 index 00000000..81ac9834 --- /dev/null +++ b/tests/Platform/docker/Dockerfile84 @@ -0,0 +1,24 @@ +FROM php:8.4.0beta4-cli + +# MSSQL +RUN apt update \ + && apt install -y gnupg2 \ + && apt install -y unixodbc-dev unixodbc \ + && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ + && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | tee /etc/apt/sources.list.d/mssql-tools.list \ + && apt update \ + && ACCEPT_EULA=Y apt install -y msodbcsql17 \ + && pecl install sqlsrv \ + && pecl install pdo_sqlsrv \ + && docker-php-ext-enable sqlsrv pdo_sqlsrv + +RUN set -ex \ + && apt update \ + && apt install -y bash zip libpq-dev libsqlite3-dev \ + && pecl install xdebug-3.4 mongodb \ + && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ + && docker-php-ext-install pdo mysqli pgsql pdo_mysql pdo_pgsql pdo_sqlite \ + && docker-php-ext-enable mongodb # TODO xdebug not yet supported here + +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer + diff --git a/tests/Platform/docker/docker-compose.yml b/tests/Platform/docker/docker-compose.yml index 73596b72..4a3b0f48 100644 --- a/tests/Platform/docker/docker-compose.yml +++ b/tests/Platform/docker/docker-compose.yml @@ -63,3 +63,17 @@ services: user: ${UID:-1000}:${GID:-1000} volumes: - ../../../:/app + + php84: + depends_on: [mysql, pgsql] + build: + context: . + dockerfile: ./Dockerfile84 + environment: + MYSQL_HOST: mysql + PGSQL_HOST: pgsql + MSSQL_HOST: mssql + working_dir: /app + user: ${UID:-1000}:${GID:-1000} + volumes: + - ../../../:/app