Skip to content

Commit 5e0d6f8

Browse files
hembergerstaabm
andauthored
SQL AST: min/max/avg improvements (#587)Co-authored-by: Markus Staab <[email protected]>
* SQL AST: min/max/avg improvements Fix the avg return type extension using the following rules for its return value: * `null` if the input is only null. * `numeric-string` if non-null inputs are only integers. * `float` if _any_ non-null input is a string. Put min/max in their own return type extension, since their behavior is sufficiently different from avg. They return the input type, but if any input is a string, then the return value is stringified. * Fix avg/min/max due to confusion with ifnull * min/max do not change any types * avg converts ints to numeric-string and strings to floats * Add test cases for non-empty/falsy-string * Update pdo-mysql cache * Update tests/sqlAst/data/sql-ast-narrowing.php --------- Co-authored-by: Markus Staab <[email protected]>
1 parent eb616b3 commit 5e0d6f8

File tree

6 files changed

+3994
-1641
lines changed

6 files changed

+3994
-1641
lines changed

src/SqlAst/AvgReturnTypeExtension.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44

55
namespace staabm\PHPStanDba\SqlAst;
66

7+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
8+
use PHPStan\Type\IntersectionType;
9+
use PHPStan\Type\StringType;
710
use PHPStan\Type\Type;
811
use PHPStan\Type\TypeCombinator;
12+
use PHPStan\Type\UnionType;
913
use SqlFtw\Sql\Expression\BuiltInFunction;
1014
use SqlFtw\Sql\Expression\FunctionCall;
1115

1216
final class AvgReturnTypeExtension implements QueryFunctionReturnTypeExtension
1317
{
1418
public function isFunctionSupported(FunctionCall $expression): bool
1519
{
16-
return \in_array($expression->getFunction()->getName(), [BuiltInFunction::AVG, BuiltInFunction::MIN, BuiltInFunction::MAX], true);
20+
return \in_array($expression->getFunction()->getName(), [BuiltInFunction::AVG], true);
1721
}
1822

1923
public function getReturnType(FunctionCall $expression, QueryScope $scope): ?Type
@@ -25,8 +29,38 @@ public function getReturnType(FunctionCall $expression, QueryScope $scope): ?Typ
2529
}
2630

2731
$argType = $scope->getType($args[0]);
28-
$numberType = $argType->toNumber();
2932

30-
return TypeCombinator::addNull($numberType);
33+
if ($argType->isNull()->yes()) {
34+
return $argType;
35+
}
36+
$argType = TypeCombinator::removeNull($argType);
37+
38+
if ($argType instanceof UnionType) {
39+
$newTypes = [];
40+
foreach ($argType->getTypes() as $type) {
41+
$newTypes[] = $this->convertToAvgType($type);
42+
}
43+
$newType = TypeCombinator::union(...$newTypes);
44+
} else {
45+
$newType = $this->convertToAvgType($argType);
46+
}
47+
48+
return TypeCombinator::addNull($newType);
49+
}
50+
51+
private function convertToAvgType(Type $argType): Type
52+
{
53+
if ($argType->isInteger()->yes()) {
54+
return new IntersectionType([
55+
new StringType(),
56+
new AccessoryNumericStringType(),
57+
]);
58+
}
59+
60+
if ($argType->isString()->yes()) {
61+
return $argType->toFloat();
62+
}
63+
64+
return $argType;
3165
}
3266
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace staabm\PHPStanDba\SqlAst;
6+
7+
use PHPStan\Type\Type;
8+
use PHPStan\Type\TypeCombinator;
9+
use SqlFtw\Sql\Expression\BuiltInFunction;
10+
use SqlFtw\Sql\Expression\FunctionCall;
11+
12+
final class MinMaxReturnTypeExtension implements QueryFunctionReturnTypeExtension
13+
{
14+
public function isFunctionSupported(FunctionCall $expression): bool
15+
{
16+
return \in_array($expression->getFunction()->getName(), [BuiltInFunction::MIN, BuiltInFunction::MAX], true);
17+
}
18+
19+
public function getReturnType(FunctionCall $expression, QueryScope $scope): ?Type
20+
{
21+
$args = $expression->getArguments();
22+
23+
if (1 !== \count($args)) {
24+
return null;
25+
}
26+
27+
$argType = $scope->getType($args[0]);
28+
29+
return TypeCombinator::addNull($argType);
30+
}
31+
}

src/SqlAst/QueryScope.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public function __construct(Table $fromTable, array $joinedTables)
6969
new IsNullReturnTypeExtension(),
7070
new AbsReturnTypeExtension(),
7171
new RoundReturnTypeExtension(),
72+
new MinMaxReturnTypeExtension(),
7273
];
7374
}
7475

0 commit comments

Comments
 (0)