Skip to content

Commit ed9f9fc

Browse files
Update Min & Max function to compare with null values.
1 parent 25c92ff commit ed9f9fc

File tree

2 files changed

+195
-4
lines changed

2 files changed

+195
-4
lines changed

src/Processor/Expression/FunctionEvaluator.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,14 +435,20 @@ private static function sqlMin(
435435

436436
$value = Evaluator::evaluate($conn, $scope, $expr, $row, $result);
437437

438-
if (!\is_scalar($value)) {
438+
if (!\is_scalar($value) && !\is_null($value)) {
439439
throw new \TypeError('Bad min value');
440440
}
441441

442442
$values[] = $value;
443443
}
444444

445-
return self::castAggregate(\min($values), $expr, $result);
445+
$min_value = \min($values);
446+
447+
if ($min_value === null) {
448+
return null;
449+
}
450+
451+
return self::castAggregate($min_value, $expr, $result);
446452
}
447453

448454
/**
@@ -470,14 +476,20 @@ private static function sqlMax(
470476

471477
$value = Evaluator::evaluate($conn, $scope, $expr, $row, $result);
472478

473-
if (!\is_scalar($value)) {
479+
if (!\is_scalar($value) && !\is_null($value)) {
474480
throw new \TypeError('Bad max value');
475481
}
476482

477483
$values[] = $value;
478484
}
479485

480-
return self::castAggregate(\max($values), $expr, $result);
486+
$max_value = \max($values);
487+
488+
if ($max_value === null) {
489+
return null;
490+
}
491+
492+
return self::castAggregate($max_value, $expr, $result);
481493
}
482494

483495
/**

tests/FunctionEvaluatorTest.php

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vimeo\MysqlEngine\Tests;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Vimeo\MysqlEngine\FakePdoInterface;
9+
use Vimeo\MysqlEngine\Processor\Expression\FunctionEvaluator;
10+
use Vimeo\MysqlEngine\Processor\QueryResult;
11+
use Vimeo\MysqlEngine\Processor\Scope;
12+
use Vimeo\MysqlEngine\Query\Expression\ColumnExpression;
13+
use Vimeo\MysqlEngine\Query\Expression\FunctionExpression;
14+
15+
class FunctionEvaluatorTest extends TestCase
16+
{
17+
18+
public function dataFunction(): array
19+
{
20+
return [
21+
'numeric' => ['SELECT ?', 1],
22+
':field' => ['SELECT :field', ':field'],
23+
'field' => ['SELECT :field', 'field'],
24+
];
25+
}
26+
27+
/**
28+
* @dataProvider maxValueProvider
29+
*/
30+
public function testSqlMax(array $rows, ?int $expected) : void
31+
{
32+
$conn = $this->createMock(FakePdoInterface::class);
33+
$scope = $this->createMock(Scope::class);
34+
$queryResult = $this->createMock(QueryResult::class);
35+
/** @var array<int, non-empty-array<string, mixed>> $rows */
36+
$queryResult->rows = $rows;
37+
38+
$token = new \Vimeo\MysqlEngine\Parser\Token(
39+
\Vimeo\MysqlEngine\TokenType::SQLFUNCTION,
40+
'MAX',
41+
'MAX',
42+
0
43+
);
44+
45+
$exp = new ColumnExpression(
46+
new \Vimeo\MysqlEngine\Parser\Token(\Vimeo\MysqlEngine\TokenType::IDENTIFIER, "value", '', 0)
47+
);
48+
49+
$functionExpr = new FunctionExpression(
50+
$token,
51+
[$exp],
52+
false
53+
);
54+
55+
$refMethod = new \ReflectionMethod(FunctionEvaluator::class, 'sqlMax');
56+
$refMethod->setAccessible(true);
57+
58+
if ($expected === -1) {
59+
$this->expectException(\TypeError::class);
60+
$this->expectExceptionMessage('Bad max value');
61+
}
62+
63+
/** @var int|null $actual */
64+
$actual = $refMethod->invoke(null, $conn, $scope, $functionExpr, $queryResult);
65+
66+
if ($expected !== -1) {
67+
$this->assertSame($expected, $actual);
68+
}
69+
}
70+
71+
/**
72+
* @dataProvider minValueProvider
73+
*/
74+
public function testSqlMin(array $rows, ?int $expected) : void
75+
{
76+
$conn = $this->createMock(FakePdoInterface::class);
77+
$scope = $this->createMock(Scope::class);
78+
$queryResult = $this->createMock(QueryResult::class);
79+
/** @var array<int, non-empty-array<string, mixed>> $rows */
80+
$queryResult->rows = $rows;
81+
82+
$token = new \Vimeo\MysqlEngine\Parser\Token(
83+
\Vimeo\MysqlEngine\TokenType::SQLFUNCTION,
84+
'MIN',
85+
'MIN',
86+
0
87+
);
88+
89+
$exp = new ColumnExpression(
90+
new \Vimeo\MysqlEngine\Parser\Token(\Vimeo\MysqlEngine\TokenType::IDENTIFIER, "value", '', 0)
91+
);
92+
93+
$functionExpr = new FunctionExpression(
94+
$token,
95+
[$exp],
96+
false
97+
);
98+
99+
$refMethod = new \ReflectionMethod(FunctionEvaluator::class, 'sqlMin');
100+
$refMethod->setAccessible(true);
101+
102+
if ($expected === -1) {
103+
$this->expectException(\TypeError::class);
104+
$this->expectExceptionMessage('Bad min value');
105+
}
106+
107+
/** @var int|null $actual */
108+
$actual = $refMethod->invoke(null, $conn, $scope, $functionExpr, $queryResult);
109+
110+
if ($expected !== -1) {
111+
$this->assertSame($expected, $actual);
112+
}
113+
}
114+
115+
116+
public static function maxValueProvider(): array
117+
{
118+
return [
119+
'null when no rows' => [
120+
'rows' => [],
121+
'expected' => null,
122+
],
123+
'max of scalar values' => [
124+
'rows' => [
125+
['value' => 10],
126+
['value' => 25],
127+
['value' => 5],
128+
],
129+
'expected' => 25,
130+
],
131+
'null values mixed in' => [
132+
'rows' => [
133+
['value' => null],
134+
['value' => 7],
135+
['value' => null],
136+
],
137+
'expected' => 7,
138+
],
139+
'non scalar values' => [
140+
'rows' => [
141+
['value' => ['test']],
142+
],
143+
'expected' => -1,
144+
],
145+
];
146+
}
147+
148+
public static function minValueProvider(): array
149+
{
150+
return [
151+
'null when no rows' => [
152+
'rows' => [],
153+
'expected' => null,
154+
],
155+
'min of scalar values' => [
156+
'rows' => [
157+
['value' => 10],
158+
['value' => 25],
159+
['value' => 5],
160+
],
161+
'expected' => 5,
162+
],
163+
'null values mixed in' => [
164+
'rows' => [
165+
['value' => null],
166+
['value' => 7],
167+
['value' => null],
168+
],
169+
'expected' => null,
170+
],
171+
'non scalar values' => [
172+
'rows' => [
173+
['value' => ['test']],
174+
],
175+
'expected' => -1,
176+
],
177+
];
178+
}
179+
}

0 commit comments

Comments
 (0)