From f36b6d609fca0f68b8180a601b4a81b5e38ac43e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 20 Nov 2024 16:50:52 +0100 Subject: [PATCH] Fix tests --- composer.json | 2 +- src/Type/Doctrine/Descriptors/BigIntType.php | 3 +- src/Type/Doctrine/Descriptors/DecimalType.php | 9 +- src/Type/Doctrine/Descriptors/FloatType.php | 8 +- .../Doctrine/Query/QueryResultTypeWalker.php | 76 +++- ...eryResultTypeWalkerFetchTypeMatrixTest.php | 353 +++++++++--------- ...QueryResultTypeWalkerHydrationModeTest.php | 14 +- .../Query/QueryResultTypeWalkerTest.php | 30 +- 8 files changed, 269 insertions(+), 226 deletions(-) diff --git a/composer.json b/composer.json index 3d2655d7..8bd0d8f0 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12.6" + "phpstan/phpstan": "^1.12.12" }, "conflict": { "doctrine/collections": "<1.0", diff --git a/src/Type/Doctrine/Descriptors/BigIntType.php b/src/Type/Doctrine/Descriptors/BigIntType.php index 14b3ca2a..01b85eff 100644 --- a/src/Type/Doctrine/Descriptors/BigIntType.php +++ b/src/Type/Doctrine/Descriptors/BigIntType.php @@ -3,7 +3,6 @@ namespace PHPStan\Type\Doctrine\Descriptors; use Composer\InstalledVersions; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\IntegerType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -25,7 +24,7 @@ public function getWritableToPropertyType(): Type return new IntegerType(); } - return TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + return (new IntegerType())->toString(); } public function getWritableToDatabaseType(): Type diff --git a/src/Type/Doctrine/Descriptors/DecimalType.php b/src/Type/Doctrine/Descriptors/DecimalType.php index 64184c45..066c0d93 100644 --- a/src/Type/Doctrine/Descriptors/DecimalType.php +++ b/src/Type/Doctrine/Descriptors/DecimalType.php @@ -4,10 +4,8 @@ use Doctrine\DBAL\Connection; use PHPStan\Doctrine\Driver\DriverDetector; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -31,7 +29,7 @@ public function getType(): string public function getWritableToPropertyType(): Type { - return TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); + return (new FloatType())->toString(); } public function getWritableToDatabaseType(): Type @@ -58,10 +56,7 @@ public function getDatabaseInternalTypeForDriver(Connection $connection): Type DriverDetector::PGSQL, DriverDetector::PDO_PGSQL, ], true)) { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); + return (new FloatType())->toString(); } // not yet supported driver, return the old implementation guess diff --git a/src/Type/Doctrine/Descriptors/FloatType.php b/src/Type/Doctrine/Descriptors/FloatType.php index e475c0a2..92accffa 100644 --- a/src/Type/Doctrine/Descriptors/FloatType.php +++ b/src/Type/Doctrine/Descriptors/FloatType.php @@ -4,10 +4,7 @@ use Doctrine\DBAL\Connection; use PHPStan\Doctrine\Driver\DriverDetector; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; -use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function in_array; @@ -42,10 +39,7 @@ public function getDatabaseInternalType(): Type { return TypeCombinator::union( new \PHPStan\Type\FloatType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]) + (new \PHPStan\Type\FloatType())->toString() ); } diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php index 54d836c6..d6a898dc 100644 --- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php +++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php @@ -18,7 +18,9 @@ use PHPStan\Php\PhpVersion; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -630,10 +632,10 @@ public function walkFunction($function): string $type = $this->createFloat(false); } elseif ($castedExprType->isNumericString()->yes()) { - $type = $this->createNumericString(false); + $type = $this->createNumericString(false, $castedExprType->isLowercaseString()->yes(), $castedExprType->isUppercaseString()->yes()); } else { - $type = TypeCombinator::union($this->createFloat(false), $this->createNumericString(false)); + $type = TypeCombinator::union($this->createFloat(false), $this->createNumericString(false, false, true)); } } else { @@ -746,7 +748,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type if ($this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::MYSQLI) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } if ($exprTypeNoNull->isString()->yes() && !$exprTypeNoNull->isNumericString()->yes()) { @@ -758,7 +760,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type if ($this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::PDO_PGSQL) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } return $this->generalizeConstantType($exprType, $nullable); @@ -794,7 +796,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type if ($this->driverType === DriverDetector::PDO_MYSQL || $this->driverType === DriverDetector::MYSQLI) { if ($exprTypeNoNull->isInteger()->yes()) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } if ($exprTypeNoNull->isString()->yes() && !$exprTypeNoNull->isNumericString()->yes()) { @@ -808,7 +810,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type if ($exprTypeNoNull->isInteger()->yes()) { return TypeCombinator::union( $this->createInteger($nullable), - $this->createNumericString($nullable) + $this->createNumericString($nullable, true, true) ); } @@ -845,19 +847,41 @@ private function createNonNegativeInteger(bool $nullable): Type return $nullable ? TypeCombinator::addNull($integer) : $integer; } - private function createNumericString(bool $nullable): Type + private function createNumericString(bool $nullable, bool $lowercase = false, bool $uppercase = false): Type { - $numericString = TypeCombinator::intersect( + $types = [ new StringType(), - new AccessoryNumericStringType() - ); + new AccessoryNumericStringType(), + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + + $numericString = new IntersectionType($types); return $nullable ? TypeCombinator::addNull($numericString) : $numericString; } - private function createString(bool $nullable): Type + private function createString(bool $nullable, bool $lowercase = false, bool $uppercase = false): Type { - $string = new StringType(); + if ($lowercase || $uppercase) { + $types = [ + new StringType(), + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + $string = new IntersectionType($types); + } else { + $string = new StringType(); + } + return $nullable ? TypeCombinator::addNull($string) : $string; } @@ -903,10 +927,18 @@ private function generalizeConstantType(Type $type, bool $makeNullable): Type $result = $this->createFloat($containsNull); } elseif ($typeNoNull->isNumericString()->yes()) { - $result = $this->createNumericString($containsNull); + $result = $this->createNumericString( + $containsNull, + $typeNoNull->isLowercaseString()->yes(), + $typeNoNull->isUppercaseString()->yes() + ); } elseif ($typeNoNull->isString()->yes()) { - $result = $this->createString($containsNull); + $result = $this->createString( + $containsNull, + $typeNoNull->isLowercaseString()->yes(), + $typeNoNull->isUppercaseString()->yes() + ); } else { $result = $type; @@ -1249,7 +1281,7 @@ public function walkSelectExpression($selectExpression): string // e.g. 1.0 on sqlite results to '1' with pdo_stringify on PHP 8.1, but '1.0' on PHP 8.0 with no setup // so we relax constant types and return just numeric-string to avoid those issues - $stringifiedFloat = $this->createNumericString(false); + $stringifiedFloat = $this->createNumericString(false, false, true); if ($stringify->yes()) { return $stringifiedFloat; @@ -1781,7 +1813,11 @@ private function inferPlusMinusTimesType(array $termTypes): Type } if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); + return $this->createNumericString( + $nullable, + $unionWithoutNull->toString()->isLowercaseString()->yes(), + $unionWithoutNull->toString()->isUppercaseString()->yes() + ); } if ($this->containsOnlyNumericTypes($unionWithoutNull)) { @@ -1833,7 +1869,7 @@ private function inferDivisionType(array $termTypes): Type if ($unionWithoutNull->isInteger()->yes()) { if ($this->driverType === DriverDetector::MYSQLI || $this->driverType === DriverDetector::PDO_MYSQL) { - return $this->createNumericString($nullable); + return $this->createNumericString($nullable, true, true); } elseif ($this->driverType === DriverDetector::PDO_PGSQL || $this->driverType === DriverDetector::PGSQL || $this->driverType === DriverDetector::SQLITE3 || $this->driverType === DriverDetector::PDO_SQLITE) { return $this->createInteger($nullable); } @@ -1861,7 +1897,11 @@ private function inferDivisionType(array $termTypes): Type } if ($this->containsOnlyTypes($unionWithoutNull, [new IntegerType(), $this->createNumericString(false)])) { - return $this->createNumericString($nullable); + return $this->createNumericString( + $nullable, + $unionWithoutNull->toString()->isLowercaseString()->yes(), + $unionWithoutNull->toString()->isUppercaseString()->yes() + ); } if ($this->containsOnlyTypes($unionWithoutNull, [new FloatType(), $this->createNumericString(false)])) { diff --git a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php index 28f9c2ed..03e5ed12 100644 --- a/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php +++ b/tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php @@ -25,7 +25,9 @@ use PHPStan\Platform\Entity\PlatformEntity; use PHPStan\Platform\Entity\PlatformRelatedEntity; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -925,7 +927,7 @@ public static function provideCases(): iterable yield '1 + 1 * 1 / 1 - 1' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 + 1 * 1 / 1 - 1) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1053,10 +1055,10 @@ public static function provideCases(): iterable yield 't.col_int + t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '9.1', 'sqliteResult' => 9.1, @@ -1069,10 +1071,10 @@ public static function provideCases(): iterable yield 't.col_int + t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_int + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '2.0', 'sqliteResult' => 2, @@ -1117,10 +1119,10 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '2.0', 'sqliteResult' => 2, @@ -1149,10 +1151,10 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal + t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.2', 'sqliteResult' => 0.2, @@ -1229,7 +1231,7 @@ public static function provideCases(): iterable yield 't.col_decimal + t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal + t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1277,7 +1279,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_int' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_int FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1293,7 +1295,7 @@ public static function provideCases(): iterable yield 't.col_bigint / t.col_bigint' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_bigint / t.col_bigint FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1373,10 +1375,10 @@ public static function provideCases(): iterable yield 't.col_int / t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '90.0000', 'sqliteResult' => 90.0, @@ -1389,10 +1391,10 @@ public static function provideCases(): iterable yield 't.col_int / t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_int / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1, @@ -1421,10 +1423,10 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -1437,10 +1439,10 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_decimal (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal / t.col_decimal FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1, @@ -1517,7 +1519,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1565,7 +1567,7 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_bool' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1581,7 +1583,7 @@ public static function provideCases(): iterable yield 't.col_decimal / t.col_bool (int data)' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT t.col_decimal / t.col_bool FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -1629,7 +1631,7 @@ public static function provideCases(): iterable yield 't.col_int / t.col_int_nullable' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_int / t.col_int_nullable FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), 'pdo_pgsql' => self::intOrNull(), 'pgsql' => self::intOrNull(), @@ -1709,7 +1711,7 @@ public static function provideCases(): iterable yield '1 / 1' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 / 1) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), 'pdo_pgsql' => self::int(), 'pgsql' => self::int(), @@ -1725,10 +1727,10 @@ public static function provideCases(): iterable yield '1 / 1.0' => [ 'data' => self::dataDefault(), 'select' => 'SELECT (1 / 1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -1743,8 +1745,8 @@ public static function provideCases(): iterable 'select' => 'SELECT (1 / 1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -1949,10 +1951,10 @@ public static function provideCases(): iterable yield 'COALESCE(t.col_decimal, t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT COALESCE(t.col_decimal, t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -1997,11 +1999,11 @@ public static function provideCases(): iterable yield 't.col_decimal' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_decimal FROM %s t', - 'mysql' => self::numericString(), - 'sqlite' => self::numericString(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), - 'mssql' => self::numericString(), + 'mysql' => self::numericString(false, true), + 'sqlite' => self::numericString(false, true), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), + 'mssql' => self::numericString(false, true), 'mysqlResult' => '0.1', 'sqliteResult' => '0.1', 'pdoPgsqlResult' => '0.1', @@ -2029,11 +2031,11 @@ public static function provideCases(): iterable yield 't.col_bigint' => [ 'data' => self::dataDefault(), 'select' => 'SELECT t.col_bigint FROM %s t', - 'mysql' => self::hasDbal4() ? self::int() : self::numericString(), - 'sqlite' => self::hasDbal4() ? self::int() : self::numericString(), - 'pdo_pgsql' => self::hasDbal4() ? self::int() : self::numericString(), - 'pgsql' => self::hasDbal4() ? self::int() : self::numericString(), - 'mssql' => self::hasDbal4() ? self::int() : self::numericString(), + 'mysql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'sqlite' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'pdo_pgsql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'pgsql' => self::hasDbal4() ? self::int() : self::numericString(true, true), + 'mssql' => self::hasDbal4() ? self::int() : self::numericString(true, true), 'mysqlResult' => self::hasDbal4() ? 2147483648 : '2147483648', 'sqliteResult' => self::hasDbal4() ? 2147483648 : '2147483648', 'pdoPgsqlResult' => self::hasDbal4() ? 2147483648 : '2147483648', @@ -2125,10 +2127,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.10000', 'sqliteResult' => 0.1, @@ -2141,10 +2143,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT AVG(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2173,10 +2175,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '9.0000', 'sqliteResult' => 9.0, @@ -2189,7 +2191,7 @@ public static function provideCases(): iterable yield 'AVG(t.col_bool)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_bool) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -2221,10 +2223,10 @@ public static function provideCases(): iterable yield 'AVG(1)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -2301,10 +2303,10 @@ public static function provideCases(): iterable yield 'AVG(1) + GROUP BY' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1) FROM %s t GROUP BY t.col_int', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0000', 'sqliteResult' => 1.0, @@ -2317,10 +2319,10 @@ public static function provideCases(): iterable yield 'AVG(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2333,10 +2335,10 @@ public static function provideCases(): iterable yield 'AVG(1e0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.00000', 'sqliteResult' => 1.0, @@ -2349,10 +2351,10 @@ public static function provideCases(): iterable yield 'AVG(t.col_bigint)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT AVG(t.col_bigint) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '2147483648.0000', 'sqliteResult' => 2147483648.0, @@ -2429,10 +2431,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -2445,10 +2447,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT SUM(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -2461,10 +2463,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '9', 'sqliteResult' => 9, @@ -2477,10 +2479,10 @@ public static function provideCases(): iterable yield '-SUM(t.col_int)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT -SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '-9', 'sqliteResult' => -9, @@ -2493,10 +2495,10 @@ public static function provideCases(): iterable yield '-SUM(t.col_int) + no data' => [ 'data' => self::dataNone(), 'select' => 'SELECT -SUM(t.col_int) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => null, 'sqliteResult' => null, @@ -2525,7 +2527,7 @@ public static function provideCases(): iterable yield 'SUM(t.col_bool)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_bool) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), 'pdo_pgsql' => null, // Undefined function 'pgsql' => null, // Undefined function @@ -2621,10 +2623,10 @@ public static function provideCases(): iterable yield 'SUM(1)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -2637,10 +2639,10 @@ public static function provideCases(): iterable yield 'SUM(1) + GROUP BY' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1) FROM %s t GROUP BY t.col_int', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), - 'pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), + 'pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -2653,10 +2655,10 @@ public static function provideCases(): iterable yield 'SUM(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -2671,8 +2673,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SUM(1e0) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -2685,10 +2687,10 @@ public static function provideCases(): iterable yield 'SUM(t.col_bigint)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT SUM(t.col_bigint) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::intOrNull(), - 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), - 'pgsql' => TypeCombinator::union(self::numericStringOrNull(), self::intOrNull()), + 'pdo_pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), + 'pgsql' => TypeCombinator::union(self::numericStringOrNull(true, true), self::intOrNull()), 'mssql' => self::mixed(), 'mysqlResult' => '2147483648', 'sqliteResult' => 2147483648, @@ -2749,10 +2751,10 @@ public static function provideCases(): iterable yield 'MAX(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT MAX(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -2765,10 +2767,10 @@ public static function provideCases(): iterable yield 'MAX(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT MAX(t.col_decimal) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(false, true), 'sqlite' => self::floatOrIntOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(false, true), + 'pgsql' => self::numericStringOrNull(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -2861,10 +2863,10 @@ public static function provideCases(): iterable yield "MAX('1')" => [ 'data' => self::dataDefault(), 'select' => "SELECT MAX('1') FROM %s t", - 'mysql' => self::numericStringOrNull(), - 'sqlite' => self::numericStringOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), + 'sqlite' => self::numericStringOrNull(true, true), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => '1', @@ -2877,10 +2879,10 @@ public static function provideCases(): iterable yield "MAX('1.0')" => [ 'data' => self::dataDefault(), 'select' => "SELECT MAX('1.0') FROM %s t", - 'mysql' => self::numericStringOrNull(), - 'sqlite' => self::numericStringOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), + 'sqlite' => self::numericStringOrNull(true, true), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => '1.0', @@ -2925,10 +2927,10 @@ public static function provideCases(): iterable yield 'MAX(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT MAX(1.0) FROM %s t', - 'mysql' => self::numericStringOrNull(), + 'mysql' => self::numericStringOrNull(true, true), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -2943,8 +2945,8 @@ public static function provideCases(): iterable 'select' => 'SELECT MAX(1e0) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericStringOrNull(), - 'pgsql' => self::numericStringOrNull(), + 'pdo_pgsql' => self::numericStringOrNull(true, true), + 'pgsql' => self::numericStringOrNull(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -2989,10 +2991,10 @@ public static function provideCases(): iterable yield 'ABS(t.col_decimal)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT ABS(t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '0.1', 'sqliteResult' => 0.1, @@ -3005,10 +3007,10 @@ public static function provideCases(): iterable yield 'ABS(t.col_decimal) + int data' => [ 'data' => self::dataAllIntLike(), 'select' => 'SELECT ABS(t.col_decimal) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(false, true), 'sqlite' => self::floatOrInt(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -3149,10 +3151,10 @@ public static function provideCases(): iterable yield 'ABS(1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT ABS(1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -3167,8 +3169,8 @@ public static function provideCases(): iterable 'select' => 'SELECT ABS(1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -3647,8 +3649,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(t.col_decimal) FROM %s t', 'mysql' => self::floatOrNull(), 'sqlite' => self::floatOrNull(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(false, true), + 'pgsql' => self::numericString(false, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -3823,8 +3825,8 @@ public static function provideCases(): iterable 'select' => 'SELECT SQRT(1.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -4045,10 +4047,10 @@ public static function provideCases(): iterable yield 'COALESCE(SUM(t.col_int_nullable), 0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(SUM(t.col_int_nullable), 0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::numericString(), self::int()), - 'pgsql' => TypeCombinator::union(self::numericString(), self::int()), + 'pdo_pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), + 'pgsql' => TypeCombinator::union(self::numericString(true, true), self::int()), 'mssql' => self::mixed(), 'mysqlResult' => '0', 'sqliteResult' => 0, @@ -4061,10 +4063,10 @@ public static function provideCases(): iterable yield 'COALESCE(SUM(ABS(t.col_int)), 0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(SUM(ABS(t.col_int)), 0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::int(), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '9', 'sqliteResult' => 9, @@ -4141,10 +4143,10 @@ public static function provideCases(): iterable yield "COALESCE(1, '1')" => [ 'data' => self::dataDefault(), 'select' => "SELECT COALESCE(1, '1') FROM %s t", - 'mysql' => self::numericString(), - 'sqlite' => TypeCombinator::union(self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'mysql' => self::numericString(true, true), + 'sqlite' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -4159,8 +4161,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1, 1.0) FROM %s t', 'mysql' => self::numericString(), 'sqlite' => TypeCombinator::union(self::int(), self::float()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1, @@ -4189,10 +4191,10 @@ public static function provideCases(): iterable yield 'COALESCE(1.0, 1.0)' => [ 'data' => self::dataDefault(), 'select' => 'SELECT COALESCE(1.0, 1.0) FROM %s t', - 'mysql' => self::numericString(), + 'mysql' => self::numericString(true, true), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => '1.0', 'sqliteResult' => 1.0, @@ -4207,8 +4209,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1e0, 1.0) FROM %s t', 'mysql' => self::float(), 'sqlite' => self::float(), - 'pdo_pgsql' => self::numericString(), - 'pgsql' => self::numericString(), + 'pdo_pgsql' => self::numericString(true, true), + 'pgsql' => self::numericString(true, true), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1.0, @@ -4223,8 +4225,8 @@ public static function provideCases(): iterable 'select' => 'SELECT COALESCE(1, 1.0, 1e0) FROM %s t', 'mysql' => self::float(), 'sqlite' => TypeCombinator::union(self::float(), self::int()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => 1.0, 'sqliteResult' => 1, @@ -4238,9 +4240,9 @@ public static function provideCases(): iterable 'data' => self::dataDefault(), 'select' => "SELECT COALESCE(1, 1.0, 1e0, '1') FROM %s t", 'mysql' => self::numericString(), - 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString()), - 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::int(), self::numericString()), + 'sqlite' => TypeCombinator::union(self::float(), self::int(), self::numericString(true, true)), + 'pdo_pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), + 'pgsql' => TypeCombinator::union(self::int(), self::numericString(true, true)), 'mssql' => self::mixed(), 'mysqlResult' => '1', 'sqliteResult' => 1, @@ -4305,8 +4307,8 @@ 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' => TypeCombinator::union(self::float(), self::numericString()), - 'pgsql' => TypeCombinator::union(self::float(), self::numericString()), + 'pdo_pgsql' => TypeCombinator::union(self::float(), self::numericString(false, true)), + 'pgsql' => TypeCombinator::union(self::float(), self::numericString(false, true)), 'mssql' => self::mixed(), 'mysqlResult' => 0.0, 'sqliteResult' => 0.0, @@ -4755,7 +4757,7 @@ private function assertInferredResultMatchesExpected( $inferredFirstItemType = $inferredType->getFirstIterableValueType(); self::assertTrue( - $inferredFirstItemType->equals($expectedFirstItemType), + $expectedFirstItemType->accepts($inferredFirstItemType, true)->yes(), sprintf( "Mismatch between inferred result and expected type\n\nDriver: %s\nConfig: %s\nDataset: %s\nDQL: %s\nSQL: %s\nPHP: %s\nReal first result: %s\nFirst item inferred as: %s\nFirst item expected type: %s\n", $driver, @@ -4842,12 +4844,20 @@ private static function boolOrNull(): Type return TypeCombinator::addNull(new BooleanType()); } - private static function numericString(): Type + private static function numericString(bool $lowercase = false, bool $uppercase = false): Type { - return new IntersectionType([ + $types = [ new StringType(), new AccessoryNumericStringType(), - ]); + ]; + if ($lowercase) { + $types[] = new AccessoryLowercaseStringType(); + } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } + + return new IntersectionType($types); } private static function string(): Type @@ -4855,12 +4865,9 @@ private static function string(): Type return new StringType(); } - private static function numericStringOrNull(): Type + private static function numericStringOrNull(bool $lowercase = false, bool $uppercase = false): Type { - return TypeCombinator::addNull(new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ])); + return TypeCombinator::addNull(self::numericString($lowercase, $uppercase)); } private static function int(): Type diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php index be915cad..d29f86ec 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerHydrationModeTest.php @@ -13,6 +13,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; @@ -141,7 +142,7 @@ public static function getTestData(): iterable yield 'getResult(object), fields' => [ self::list(self::constantArray([ - [new ConstantStringType('decimalColumn'), self::numericString()], + [new ConstantStringType('decimalColumn'), self::numericString(false, true)], [new ConstantStringType('floatColumn'), new FloatType()], ])), ' @@ -177,7 +178,7 @@ public static function getTestData(): iterable yield 'toIterable(object), fields' => [ new IterableType(new IntegerType(), self::constantArray([ - [new ConstantStringType('decimalColumn'), self::numericString()], + [new ConstantStringType('decimalColumn'), self::numericString(false, true)], [new ConstantStringType('floatColumn'), new FloatType()], ])), ' @@ -309,7 +310,7 @@ private static function list(Type $values): Type return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $values)); } - private static function numericString(bool $lowercase = false): Type + private static function numericString(bool $lowercase = false, bool $uppercase = false): Type { $types = [ new StringType(), @@ -318,6 +319,9 @@ private static function numericString(bool $lowercase = false): Type if ($lowercase) { $types[] = new AccessoryLowercaseStringType(); } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } return new IntersectionType($types); } @@ -406,14 +410,14 @@ private static function stringifies(): bool private static function floatOrStringified(): Type { return self::stringifies() - ? self::numericString() + ? self::numericString(false, true) : new FloatType(); } private static function floatOrIntOrStringified(): Type { return self::stringifies() - ? self::numericString() + ? self::numericString(false, true) : TypeCombinator::union(new FloatType(), new IntegerType()); } diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php index 905c5221..aa0ce591 100644 --- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php +++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php @@ -17,6 +17,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantFloatType; @@ -377,7 +378,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -399,7 +400,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(Many::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -420,7 +421,7 @@ public function getTestData(): iterable ]), $this->constantArray([ [new ConstantStringType('one'), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], [new ConstantStringType('intColumn'), new IntegerType()], ]) ), @@ -530,7 +531,7 @@ public function getTestData(): iterable yield 'just root entity and scalars' => [ $this->constantArray([ [new ConstantIntegerType(0), new ObjectType(One::class)], - [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString()], + [new ConstantStringType('id'), $hasDbal4 ? new IntegerType() : $this->numericString(true, true)], ]), ' SELECT o, o.id @@ -788,7 +789,7 @@ public function getTestData(): iterable [ new ConstantIntegerType(4), $this->stringifies() - ? $this->numericString() + ? $this->numericString(false, true) : TypeCombinator::union( new IntegerType(), new FloatType() @@ -1474,8 +1475,8 @@ public function getTestData(): iterable yield 'unary minus' => [ $this->constantArray([ [new ConstantStringType('minusInt'), $this->stringifies() ? new ConstantStringType('-1') : new ConstantIntegerType(-1)], - [new ConstantStringType('minusFloat'), $this->stringifies() ? $this->numericString() : new ConstantFloatType(-0.1)], - [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString(true) : IntegerRangeType::fromInterval(null, 0)], + [new ConstantStringType('minusFloat'), $this->stringifies() ? $this->numericString(false, true) : new ConstantFloatType(-0.1)], + [new ConstantStringType('minusIntRange'), $this->stringifies() ? $this->numericString(true, true) : IntegerRangeType::fromInterval(null, 0)], ]), ' SELECT -1 as minusInt, @@ -1516,7 +1517,7 @@ private function yieldConditionalDataset(): iterable $this->constantArray([ [ new ConstantIntegerType(1), - new StringType(), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), ], [ new ConstantIntegerType(2), @@ -1524,7 +1525,7 @@ private function yieldConditionalDataset(): iterable ], [ new ConstantIntegerType(3), - $this->numericString(), + $this->numericString(true, true), ], ]), ' @@ -1623,7 +1624,7 @@ private function constantArray(array $elements): Type return $builder->getArray(); } - private function numericString(bool $lowercase = false): Type + private function numericString(bool $lowercase = false, bool $uppercase = false): Type { $types = [ new StringType(), @@ -1632,6 +1633,9 @@ private function numericString(bool $lowercase = false): Type if ($lowercase) { $types[] = new AccessoryLowercaseStringType(); } + if ($uppercase) { + $types[] = new AccessoryUppercaseStringType(); + } return new IntersectionType($types); } @@ -1679,21 +1683,21 @@ private function stringifies(): bool private function intOrStringified(): Type { return $this->stringifies() - ? $this->numericString(true) + ? $this->numericString(true, true) : new IntegerType(); } private function uintOrStringified(): Type { return $this->stringifies() - ? $this->numericString(true) + ? $this->numericString(true, true) : $this->uint(); } private function floatOrStringified(): Type { return $this->stringifies() - ? $this->numericString() + ? $this->numericString(false, true) : new FloatType(); }