From 4cf5d248c947c47bc6647286121b4b2012eaca24 Mon Sep 17 00:00:00 2001 From: 1okey Date: Fri, 3 Jul 2026 15:13:10 +0300 Subject: [PATCH] feat: adds a null-safe operator to the BinaryOperatorEvaluator --- .../Expression/BinaryOperatorEvaluator.php | 10 ++++- tests/EndToEndTest.php | 38 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Processor/Expression/BinaryOperatorEvaluator.php b/src/Processor/Expression/BinaryOperatorEvaluator.php index bced418c..c7fa1020 100644 --- a/src/Processor/Expression/BinaryOperatorEvaluator.php +++ b/src/Processor/Expression/BinaryOperatorEvaluator.php @@ -113,6 +113,7 @@ public static function evaluate( case '>=': case '<': case '<=': + case '<=>': $l_value = Evaluator::evaluate($conn, $scope, $left, $row, $result); $r_value = Evaluator::evaluate($conn, $scope, $right, $row, $result); @@ -138,11 +139,18 @@ public static function evaluate( } if ($l_value === null || $r_value === null) { + // The null-safe equal operator never returns NULL: it yields 1 when both + // operands are NULL and 0 when exactly one of them is NULL. + if ($expr->operator === '<=>') { + return (($l_value === null && $r_value === null) ? 1 : 0) ^ $expr->negatedInt; + } + return null; } switch ($expr->operator) { case '=': + case '<=>': if ($as_string) { return (\strtolower((string) $l_value) === \strtolower((string) $r_value) ? 1 : 0) ^ $expr->negatedInt; @@ -327,7 +335,6 @@ public static function evaluate( case 'BINARY': case 'COLLATE': case '^': - case '<=>': case '||': case 'XOR': case 'SOUNDS': @@ -385,6 +392,7 @@ public static function getColumnSchema( case '>=': case '<': case '<=': + case '<=>': case 'LIKE': case 'IS': case 'RLIKE': diff --git a/tests/EndToEndTest.php b/tests/EndToEndTest.php index 1d5352dc..5fce3fdb 100644 --- a/tests/EndToEndTest.php +++ b/tests/EndToEndTest.php @@ -1542,7 +1542,7 @@ public function testNestedFunctions() $pdo = self::getConnectionToFullDB(); $query = $pdo->prepare(" - SELECT + SELECT SUM( TIMESTAMPDIFF( SECOND, @@ -1576,4 +1576,40 @@ public function testNestedFunctionsFromDB() $this->assertSame((int)$count, (int)$query->fetchColumn()); } + + public function testNullSafeEqualOperator(): void + { + $pdo = self::getPdo('mysql:host=localhost;dbname=testdb'); + $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + + $query = $pdo->prepare( + 'SELECT + 1 <=> 1 AS `equal_ints`, + 1 <=> 2 AS `unequal_ints`, + NULL <=> NULL AS `both_null`, + 1 <=> NULL AS `left_not_null`, + NULL <=> 1 AS `right_not_null`, + \'a\' <=> \'a\' AS `equal_strings`, + \'a\' <=> \'b\' AS `unequal_strings`' + ); + + $query->execute(); + + $results = array_map(function ($row) { + return array_map('intval', $row); + }, $query->fetchAll(\PDO::FETCH_ASSOC)); + + $this->assertSame( + [[ + 'equal_ints' => 1, + 'unequal_ints' => 0, + 'both_null' => 1, + 'left_not_null' => 0, + 'right_not_null' => 0, + 'equal_strings' => 1, + 'unequal_strings' => 0, + ]], + $results + ); + } }