Skip to content

Commit 68d846a

Browse files
authored
Merge pull request #66 from 1okey/least
Add support of `LEAST` function
2 parents 226e0fe + 98965c1 commit 68d846a

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

src/Processor/Expression/FunctionEvaluator.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public static function evaluate(
116116
return self::sqlInetAton($conn, $scope, $expr, $row, $result);
117117
case 'INET_NTOA':
118118
return self::sqlInetNtoa($conn, $scope, $expr, $row, $result);
119+
case 'LEAST':
120+
return self::sqlLeast($conn, $scope, $expr, $row, $result);
119121
}
120122

121123
throw new ProcessorException("Function " . $expr->functionName . " not implemented yet");
@@ -1618,4 +1620,60 @@ private static function sqlTimestampdiff(
16181620
throw new ProcessorException("Unsupported unit '$unit' in TIMESTAMPDIFF()");
16191621
}
16201622
}
1623+
1624+
/**
1625+
* @param FakePdoInterface $conn
1626+
* @param Scope $scope
1627+
* @param FunctionExpression $expr
1628+
* @param array<string, mixed> $row
1629+
* @param QueryResult $result
1630+
*
1631+
* @return int
1632+
* @throws ProcessorException
1633+
*/
1634+
private static function sqlLeast(
1635+
FakePdoInterface $conn,
1636+
Scope $scope,
1637+
FunctionExpression $expr,
1638+
array $row,
1639+
QueryResult $result
1640+
)
1641+
{
1642+
$args = $expr->args;
1643+
1644+
if (\count($args) < 2) {
1645+
throw new ProcessorException("Incorrect parameter count in the call to native function 'LEAST'");
1646+
}
1647+
1648+
$is_any_float = false;
1649+
$is_any_string = false;
1650+
$precision = 0;
1651+
$evaluated_args = [];
1652+
1653+
foreach ($args as $arg) {
1654+
$evaluated_arg = Evaluator::evaluate($conn, $scope, $arg, $row, $result);
1655+
if (is_null($evaluated_arg)) {
1656+
return null;
1657+
}
1658+
1659+
if (is_float($evaluated_arg)) {
1660+
$is_any_float = true;
1661+
$precision = max($precision, strlen(substr(strrchr(strval($evaluated_arg), "."), 1)));
1662+
}
1663+
1664+
$is_any_string = $is_any_string || is_string($evaluated_arg);
1665+
$evaluated_args[] = $evaluated_arg;
1666+
}
1667+
1668+
if ($is_any_string) {
1669+
$evaluated_str_args = array_map(fn($evaluated_arg) => strval($evaluated_arg), $evaluated_args);
1670+
return min(...$evaluated_str_args);
1671+
}
1672+
1673+
if ($is_any_float) {
1674+
return number_format(min(...$evaluated_args), $precision);
1675+
}
1676+
1677+
return min(...$evaluated_args);
1678+
}
16211679
}

tests/EndToEndTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,4 +1435,82 @@ private static function getConnectionToFullDB(bool $emulate_prepares = true, boo
14351435

14361436
return $pdo;
14371437
}
1438+
1439+
/**
1440+
* @dataProvider leastArgumentsProvider
1441+
* @param array $args
1442+
* @param string|int|float|null $expected_value
1443+
*/
1444+
public function testLeast($args, $expected_value): void
1445+
{
1446+
// Get a PDO instance for MySQL.
1447+
$pdo = self::getPdo('mysql:host=localhost;dbname=testdb');
1448+
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
1449+
1450+
$args_str = implode(', ', array_map(fn ($arg) => is_null($arg) ? 'null' : strval($arg), $args));
1451+
$query = $pdo->prepare(sprintf('SELECT LEAST(%s) as result', $args_str),);
1452+
$query->execute();
1453+
1454+
$result = $query->fetch(\PDO::FETCH_ASSOC);
1455+
$this->assertEquals(
1456+
['result' => $expected_value], $result,
1457+
sprintf('Actual result is does not match the expected. Actual is: %s', print_r($result, true)));
1458+
}
1459+
1460+
public function leastArgumentsProvider(): iterable
1461+
{
1462+
yield 'Should properly work with at least one \'null\' argument' => [
1463+
'args' => [1,2,null,42],
1464+
'expected_value' => null
1465+
];
1466+
yield 'Should properly get the least integer argument' => [
1467+
'args' => [-1, 1,2,42],
1468+
'expected_value' => '-1'
1469+
];
1470+
yield 'Should properly work with decimal argument' => [
1471+
'args' => [0.00, 0.1,2,42, -0.001],
1472+
'expected_value' => '-0.001'
1473+
];
1474+
yield 'Should return proper precision if any argument is a float' => [
1475+
'args' => [1, 2.0001 , 42, 1.001],
1476+
'expected_value' => '1.0000'
1477+
];
1478+
yield 'Should properly work with at least one string argument' => [
1479+
'args' => [1,2, "'null'", "'nulla'"],
1480+
'expected_value' => '1'
1481+
];
1482+
yield 'Should properly work all string args' => [
1483+
'args' => ["'A'","'B'","'C'"],
1484+
'expected_value' => 'A'
1485+
];
1486+
yield 'Should lexicographically compare #1' => [
1487+
'args' => ["'AA'","'AB'","'AC'"],
1488+
'expected_value' => 'AA'
1489+
];
1490+
yield 'Should lexicographically compare #2' => [
1491+
'args' => ["'AA'","'AB'","'AC'", 1],
1492+
'expected_value' => '1'
1493+
];
1494+
}
1495+
1496+
/** @dataProvider leastWithExceptionProvider */
1497+
public function testLeastThrowsExceptionWithWrongArgumentCount(array $args): void
1498+
{
1499+
$this->expectException(\UnexpectedValueException::class);
1500+
$this->expectExceptionMessage('Incorrect parameter count in the call to native function \'LEAST\'');
1501+
1502+
$pdo = self::getPdo('mysql:host=localhost;dbname=testdb');
1503+
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
1504+
1505+
$args_str = implode(', ', array_map(fn ($arg) => strval($arg), $args));
1506+
$query = $pdo->prepare(sprintf('SELECT LEAST(%s)', $args_str),);
1507+
1508+
$query->execute();
1509+
}
1510+
1511+
public function leastWithExceptionProvider(): iterable
1512+
{
1513+
yield ['Should fail with single argument' => [1]];
1514+
yield ['Should fail without any arguments' => []];
1515+
}
14381516
}

0 commit comments

Comments
 (0)