Skip to content

Commit 5705fc5

Browse files
committed
analyse GREATEST and LEAST functions
1 parent 0b4b931 commit 5705fc5

File tree

7 files changed

+9662
-2985
lines changed

7 files changed

+9662
-2985
lines changed

src/Database/FunctionInfo/FunctionInfoRegistryFactory.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ public function createDefaultFunctionInfos(): array
2727
new DateAddSub(),
2828
new DateFormat(),
2929
new FoundRows(),
30-
new IfFunction(),
30+
new GreatestLeast(),
3131
new GroupConcat(),
32+
new IfFunction(),
3233
new LowerUpper(),
3334
new MaxMin(),
3435
new Now(),
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MariaStan\Database\FunctionInfo;
6+
7+
use MariaStan\Analyser\AnalyserConditionTypeEnum;
8+
use MariaStan\Analyser\ExprTypeResult;
9+
use MariaStan\Ast\Expr\FunctionCall\FunctionCall;
10+
use MariaStan\Parser\Exception\ParserException;
11+
use MariaStan\Schema\DbType\DateTimeType;
12+
use MariaStan\Schema\DbType\DbType;
13+
use MariaStan\Schema\DbType\DbTypeEnum;
14+
use MariaStan\Schema\DbType\FloatType;
15+
use MariaStan\Schema\DbType\NullType;
16+
17+
use function array_fill;
18+
use function count;
19+
20+
final class GreatestLeast implements FunctionInfo
21+
{
22+
/** @inheritDoc */
23+
public function getSupportedFunctionNames(): array
24+
{
25+
return ['GREATEST', 'LEAST'];
26+
}
27+
28+
public function getFunctionType(): FunctionTypeEnum
29+
{
30+
return FunctionTypeEnum::SIMPLE;
31+
}
32+
33+
public function checkSyntaxErrors(FunctionCall $functionCall): void
34+
{
35+
$args = $functionCall->getArguments();
36+
$argCount = count($args);
37+
38+
if ($argCount >= 2) {
39+
return;
40+
}
41+
42+
throw new ParserException(
43+
FunctionInfoHelper::createArgumentCountErrorMessageMin(
44+
$functionCall->getFunctionName(),
45+
2,
46+
$argCount,
47+
),
48+
);
49+
}
50+
51+
/** @inheritDoc */
52+
public function getInnerConditions(?AnalyserConditionTypeEnum $condition, array $arguments): array
53+
{
54+
// TRUTHY(LEAST(A, ...)) => NOT_NULL(A, ...)
55+
// FALSY(LEAST(A, ...)) => NOT_NULL(A, ...)
56+
// NOT_NULL(LEAST(A, ...)) => NOT_NULL(A, ...)
57+
// NULL(LEAST(A, ...)) => NULL(A) || NULL(...)
58+
$innerCondition = match ($condition) {
59+
null, AnalyserConditionTypeEnum::NULL => $condition,
60+
// TODO: Change it to NOT_NULL after 10.11.8 https://jira.mariadb.org/browse/MDEV-21034
61+
AnalyserConditionTypeEnum::TRUTHY => null,
62+
default => AnalyserConditionTypeEnum::NOT_NULL,
63+
};
64+
65+
return array_fill(0, count($arguments), $innerCondition);
66+
}
67+
68+
/**
69+
* @inheritDoc
70+
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
71+
*/
72+
public function getReturnType(
73+
FunctionCall $functionCall,
74+
array $argumentTypes,
75+
?AnalyserConditionTypeEnum $condition,
76+
bool $isNonEmptyAggResultSet,
77+
): ExprTypeResult {
78+
$leftType = $argumentTypes[0]->type;
79+
$isNullable = $argumentTypes[0]->isNullable;
80+
$knowledgeBase = $argumentTypes[0]->knowledgeBase;
81+
$i = 1;
82+
$argCount = count($argumentTypes);
83+
84+
while ($i < $argCount) {
85+
$rightKnowledgeBase = $argumentTypes[$i]->knowledgeBase;
86+
$rightType = $argumentTypes[$i]->type;
87+
$isNullable = $isNullable || $argumentTypes[$i]->isNullable;
88+
$i++;
89+
$leftType = self::castToCommonType($leftType, $rightType);
90+
$knowledgeBase = $rightKnowledgeBase === null
91+
? null
92+
: match ($condition) {
93+
null => null,
94+
AnalyserConditionTypeEnum::NULL => $knowledgeBase?->or($rightKnowledgeBase),
95+
default => $knowledgeBase?->and($rightKnowledgeBase),
96+
};
97+
}
98+
99+
return new ExprTypeResult($leftType, $isNullable, null, $knowledgeBase);
100+
}
101+
102+
private static function castToCommonType(DbType $leftType, DbType $rightType): DbType
103+
{
104+
$lt = $leftType::getTypeEnum();
105+
$rt = $rightType::getTypeEnum();
106+
$typesInvolved = [
107+
$lt->value => 1,
108+
$rt->value => 1,
109+
];
110+
111+
if (isset($typesInvolved[DbTypeEnum::NULL->value])) {
112+
return new NullType();
113+
}
114+
115+
if (isset($typesInvolved[DbTypeEnum::DATETIME->value])) {
116+
return new DateTimeType();
117+
}
118+
119+
if (isset($typesInvolved[DbTypeEnum::VARCHAR->value]) && count($typesInvolved) === 2) {
120+
return new FloatType();
121+
}
122+
123+
return FunctionInfoHelper::castToCommonType($leftType, $rightType);
124+
}
125+
}

tests/Analyser/AnalyserTest.php

+9
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,8 @@ private static function provideValidFunctionCallTestData(): iterable
988988
$selects["COALESCE({$label1}, {$label2}, int)"] = "SELECT COALESCE(NULL, {$value1}, {$value2}, 9)";
989989
$selects["ROUND({$label1}, {$label2})"] = "SELECT ROUND({$value1}, {$value2})";
990990
$selects["CONCAT({$label1}, {$label2})"] = "SELECT CONCAT({$value1}, {$value2})";
991+
$selects["LEAST({$label1}, {$label2})"] = "SELECT LEAST({$value1}, {$value2})";
992+
$selects["GREATEST({$label1}, {$label2})"] = "SELECT GREATEST({$value1}, {$value2})";
991993
}
992994

993995
$selects["TRIM({$label1})"] = "SELECT TRIM({$value1})";
@@ -1002,6 +1004,8 @@ private static function provideValidFunctionCallTestData(): iterable
10021004
$selects["UPPER({$label1})"] = "SELECT UPPER({$value1})";
10031005
$selects["UCASE({$label1})"] = "SELECT UCASE({$value1})";
10041006
$selects["ABS({$label1})"] = "SELECT ABS({$value1})";
1007+
$selects["LEAST({$label1}, {$label1}, {$label1})"] = "SELECT LEAST({$value1}, {$value1}, {$value1})";
1008+
$selects["GREATEST({$label1}, {$label1}, {$label1})"] = "SELECT GREATEST({$value1}, {$value1}, {$value1})";
10051009
}
10061010

10071011
// TODO: figure out the context in which the function is called and adjust the return type accordingly.
@@ -1735,6 +1739,11 @@ private static function provideValidNullabilityOperatorTestData(): iterable
17351739
'ABS(col_int) IS NOT NULL',
17361740
'ABS(col_int)',
17371741
'NOT ABS(col_int)',
1742+
'LEAST(col_int, col_vchar) IS NOT NULL',
1743+
// TODO: Enable this after 10.11.8. https://jira.mariadb.org/browse/MDEV-21034
1744+
//'LEAST(col_int, col_vchar)',
1745+
'NOT LEAST(col_int, col_vchar)',
1746+
'GREATEST(col_int, col_vchar) IS NULL',
17381747
];
17391748

17401749
foreach (['COALESCE', 'IFNULL', 'NVL'] as $coalesceFn) {

0 commit comments

Comments
 (0)