Skip to content

Commit 21a606f

Browse files
authored
Merge pull request #38 from sergiosalvatore/add-ceil-floor-support
Add CEIL()/CEILING() and FLOOR() SQL Function Support
2 parents 3c6ec7c + a6a8537 commit 21a606f

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

src/Processor/Expression/FunctionEvaluator.php

+98
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Vimeo\MysqlEngine\Query\Expression\FunctionExpression;
1212
use Vimeo\MysqlEngine\Query\Expression\IntervalOperatorExpression;
1313
use Vimeo\MysqlEngine\Schema\Column;
14+
use Vimeo\MysqlEngine\TokenType;
1415

1516
final class FunctionEvaluator
1617
{
@@ -91,6 +92,11 @@ public static function evaluate(
9192
return self::sqlDateAdd($conn, $scope, $expr, $row, $result);
9293
case 'ROUND':
9394
return self::sqlRound($conn, $scope, $expr, $row, $result);
95+
case 'CEIL':
96+
case 'CEILING':
97+
return self::sqlCeiling($conn, $scope, $expr, $row, $result);
98+
case 'FLOOR':
99+
return self::sqlFloor($conn, $scope, $expr, $row, $result);
94100
case 'DATEDIFF':
95101
return self::sqlDateDiff($conn, $scope, $expr, $row, $result);
96102
case 'DAY':
@@ -138,8 +144,34 @@ public static function getColumnSchema(
138144

139145
case 'MOD':
140146
return new Column\IntColumn(false, 10);
147+
141148
case 'AVG':
142149
return new Column\FloatColumn(10, 2);
150+
151+
case 'CEIL':
152+
case 'CEILING':
153+
case 'FLOOR':
154+
// from MySQL docs: https://dev.mysql.com/doc/refman/5.6/en/mathematical-functions.html#function_ceil
155+
// For exact-value numeric arguments, the return value has an exact-value numeric type. For string or
156+
// floating-point arguments, the return value has a floating-point type. But...
157+
//
158+
// mysql> CREATE TEMPORARY TABLE `temp` SELECT FLOOR(1.2);
159+
// Query OK, 1 row affected (0.00 sec)
160+
// Records: 1 Duplicates: 0 Warnings: 0
161+
//
162+
// mysql> describe temp;
163+
// +------------+--------+------+-----+---------+-------+
164+
// | Field | Type | Null | Key | Default | Extra |
165+
// +------------+--------+------+-----+---------+-------+
166+
// | FLOOR(1.2) | bigint | NO | | 0 | NULL |
167+
// +------------+--------+------+-----+---------+-------+
168+
// 1 row in set (0.00 sec)
169+
if ($expr->args[0]->getType() == TokenType::STRING_CONSTANT) {
170+
return new Column\DoubleColumn(10, 2);
171+
}
172+
173+
return new Column\BigInt(false, 10);
174+
143175
case 'IF':
144176
$if = Evaluator::getColumnSchema($expr->args[1], $scope, $columns);
145177
$else = Evaluator::getColumnSchema($expr->args[2], $scope, $columns);
@@ -1377,6 +1409,72 @@ private static function sqlInetNtoa(
13771409
return long2ip((int)$subject);
13781410
}
13791411

1412+
/**
1413+
* @param array<string, mixed> $row
1414+
* @return float|0
1415+
*/
1416+
private static function sqlCeiling(
1417+
FakePdoInterface $conn,
1418+
Scope $scope,
1419+
FunctionExpression $expr,
1420+
array $row,
1421+
QueryResult $result
1422+
) {
1423+
$args = $expr->args;
1424+
1425+
if (\count($args) !== 1) {
1426+
throw new ProcessorException("MySQL CEILING function must be called with one argument (got " . count($args) . ")");
1427+
}
1428+
1429+
$subject = Evaluator::evaluate($conn, $scope, $args[0], $row, $result);
1430+
1431+
if (!is_numeric($subject)) {
1432+
// CEILING() returns 0 if it does not understand its argument.
1433+
return 0;
1434+
}
1435+
1436+
$value = ceil(floatval($subject));
1437+
1438+
if (!$value) {
1439+
return 0;
1440+
}
1441+
1442+
return $value;
1443+
}
1444+
1445+
/**
1446+
* @param array<string, mixed> $row
1447+
* @return float|0
1448+
*/
1449+
private static function sqlFloor(
1450+
FakePdoInterface $conn,
1451+
Scope $scope,
1452+
FunctionExpression $expr,
1453+
array $row,
1454+
QueryResult $result
1455+
) {
1456+
$args = $expr->args;
1457+
1458+
if (\count($args) !== 1) {
1459+
throw new ProcessorException("MySQL FLOOR function must be called with one argument");
1460+
}
1461+
1462+
$subject = Evaluator::evaluate($conn, $scope, $args[0], $row, $result);
1463+
1464+
if (!is_numeric($subject)) {
1465+
// FLOOR() returns 0 if it does not understand its argument.
1466+
return 0;
1467+
}
1468+
1469+
$value = floor(floatval($subject));
1470+
1471+
if (!$value) {
1472+
return 0;
1473+
}
1474+
1475+
return $value;
1476+
}
1477+
13801478
private static function getPhpIntervalFromExpression(
13811479
FakePdoInterface $conn,
13821480
Scope $scope,

tests/EndToEndTest.php

+46
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,52 @@ public function testRound()
607607
);
608608
}
609609

610+
public function testCeil()
611+
{
612+
$pdo = self::getPdo('mysql:foo');
613+
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
614+
615+
$query = $pdo->prepare('SELECT CEIL(3.1) AS a, CEILING(4.7) AS b, CEIL("abc") AS c, CEIL(5) AS d, CEIL("5.5") AS e');
616+
617+
$query->execute();
618+
619+
$this->assertSame(
620+
[
621+
[
622+
'a' => 4,
623+
'b' => 5,
624+
'c' => 0.0,
625+
'd' => 5,
626+
'e' => 6.0,
627+
],
628+
],
629+
$query->fetchAll(\PDO::FETCH_ASSOC)
630+
);
631+
}
632+
633+
public function testFloor()
634+
{
635+
$pdo = self::getPdo('mysql:foo');
636+
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
637+
638+
$query = $pdo->prepare('SELECT FLOOR(3.1) AS a, FLOOR(4.7) AS b, FLOOR("abc") AS c, FLOOR(5) AS d, FLOOR("6.5") AS e');
639+
640+
$query->execute();
641+
642+
$this->assertSame(
643+
[
644+
[
645+
'a' => 3,
646+
'b' => 4,
647+
'c' => 0.0,
648+
'd' => 5,
649+
'e' => 6.0,
650+
],
651+
],
652+
$query->fetchAll(\PDO::FETCH_ASSOC)
653+
);
654+
}
655+
610656
public function testIsInFullSubquery()
611657
{
612658
$pdo = self::getConnectionToFullDB(false);

0 commit comments

Comments
 (0)