Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/Processor/Expression/FunctionEvaluator.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public static function evaluate(
return self::sqlInetAton($conn, $scope, $expr, $row, $result);
case 'INET_NTOA':
return self::sqlInetNtoa($conn, $scope, $expr, $row, $result);
case 'LEAST':
return self::sqlLeast($conn, $scope, $expr, $row, $result);
}

throw new ProcessorException("Function " . $expr->functionName . " not implemented yet");
Expand Down Expand Up @@ -1618,4 +1620,60 @@ private static function sqlTimestampdiff(
throw new ProcessorException("Unsupported unit '$unit' in TIMESTAMPDIFF()");
}
}

/**
* @param FakePdoInterface $conn
* @param Scope $scope
* @param FunctionExpression $expr
* @param array<string, mixed> $row
* @param QueryResult $result
*
* @return int
* @throws ProcessorException
*/
private static function sqlLeast(
FakePdoInterface $conn,
Scope $scope,
FunctionExpression $expr,
array $row,
QueryResult $result
)
{
$args = $expr->args;

if (\count($args) < 2) {
throw new ProcessorException("Incorrect parameter count in the call to native function 'LEAST'");
}

$is_any_float = false;
$is_any_string = false;
$precision = 0;
$evaluated_args = [];

foreach ($args as $arg) {
$evaluated_arg = Evaluator::evaluate($conn, $scope, $arg, $row, $result);
if (is_null($evaluated_arg)) {
return null;
}

if (is_float($evaluated_arg)) {
$is_any_float = true;
$precision = max($precision, strlen(substr(strrchr(strval($evaluated_arg), "."), 1)));
}

$is_any_string = $is_any_string || is_string($evaluated_arg);
$evaluated_args[] = $evaluated_arg;
}

if ($is_any_string) {
$evaluated_str_args = array_map(fn($evaluated_arg) => strval($evaluated_arg), $evaluated_args);
return min(...$evaluated_str_args);
}

if ($is_any_float) {
return number_format(min(...$evaluated_args), $precision);
}

return min(...$evaluated_args);
}
}
78 changes: 78 additions & 0 deletions tests/EndToEndTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1435,4 +1435,82 @@ private static function getConnectionToFullDB(bool $emulate_prepares = true, boo

return $pdo;
}

/**
* @dataProvider leastArgumentsProvider
* @param array $args
* @param string|int|float|null $expected_value
*/
public function testLeast($args, $expected_value): void
{
// Get a PDO instance for MySQL.
$pdo = self::getPdo('mysql:host=localhost;dbname=testdb');
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);

$args_str = implode(', ', array_map(fn ($arg) => is_null($arg) ? 'null' : strval($arg), $args));
$query = $pdo->prepare(sprintf('SELECT LEAST(%s) as result', $args_str),);
$query->execute();

$result = $query->fetch(\PDO::FETCH_ASSOC);
$this->assertEquals(
['result' => $expected_value], $result,
sprintf('Actual result is does not match the expected. Actual is: %s', print_r($result, true)));
}

public function leastArgumentsProvider(): iterable
{
yield 'Should properly work with at least one \'null\' argument' => [
'args' => [1,2,null,42],
'expected_value' => null
];
yield 'Should properly get the least integer argument' => [
'args' => [-1, 1,2,42],
'expected_value' => '-1'
];
yield 'Should properly work with decimal argument' => [
'args' => [0.00, 0.1,2,42, -0.001],
'expected_value' => '-0.001'
];
yield 'Should return proper precision if any argument is a float' => [
'args' => [1, 2.0001 , 42, 1.001],
'expected_value' => '1.0000'
];
yield 'Should properly work with at least one string argument' => [
'args' => [1,2, "'null'", "'nulla'"],
'expected_value' => '1'
];
yield 'Should properly work all string args' => [
'args' => ["'A'","'B'","'C'"],
'expected_value' => 'A'
];
yield 'Should lexicographically compare #1' => [
'args' => ["'AA'","'AB'","'AC'"],
'expected_value' => 'AA'
];
yield 'Should lexicographically compare #2' => [
'args' => ["'AA'","'AB'","'AC'", 1],
'expected_value' => '1'
];
}

/** @dataProvider leastWithExceptionProvider */
public function testLeastThrowsExceptionWithWrongArgumentCount(array $args): void
{
$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionMessage('Incorrect parameter count in the call to native function \'LEAST\'');

$pdo = self::getPdo('mysql:host=localhost;dbname=testdb');
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);

$args_str = implode(', ', array_map(fn ($arg) => strval($arg), $args));
$query = $pdo->prepare(sprintf('SELECT LEAST(%s)', $args_str),);

$query->execute();
}

public function leastWithExceptionProvider(): iterable
{
yield ['Should fail with single argument' => [1]];
yield ['Should fail without any arguments' => []];
}
}