diff --git a/src/FakePdoStatementTrait.php b/src/FakePdoStatementTrait.php index a1424c2f..abaad45c 100644 --- a/src/FakePdoStatementTrait.php +++ b/src/FakePdoStatementTrait.php @@ -129,7 +129,7 @@ public function universalExecute(?array $params = null) if ($this->realStatement) { if ($this->realStatement->execute($params) === false) { var_dump($this->sql); - throw new \UnexpectedValueException($this->realStatement->errorInfo()[2]); + throw new \UnexpectedValueException((string)$this->realStatement->errorInfo()[2]); } } @@ -632,6 +632,9 @@ private function getExecutedSql(?array $params) : string return $sql; } + /** + * @return array{0: null|string, 1: int|null, 2: null|string, 3?: mixed, 4?: mixed} + */ public function errorInfo(): array { return ['00000', 0, 'PHP MySQL Engine: errorInfo() not supported.']; diff --git a/src/FakePdoTrait.php b/src/FakePdoTrait.php index 46bb44c6..bb2d26d1 100644 --- a/src/FakePdoTrait.php +++ b/src/FakePdoTrait.php @@ -213,6 +213,9 @@ public function quote($string, $parameter_type = \PDO::PARAM_STR) return "{$quotes[0]}{$quoted}{$quotes[1]}"; } + /** + * @return array{0: null|string, 1: int|null, 2: null|string, 3?: mixed, 4?: mixed} + */ public function errorInfo(): array { return ['00000', 0, 'PHP MySQL Engine: errorInfo() not supported.']; diff --git a/src/Processor/Expression/FunctionEvaluator.php b/src/Processor/Expression/FunctionEvaluator.php index ba584014..9be41f00 100644 --- a/src/Processor/Expression/FunctionEvaluator.php +++ b/src/Processor/Expression/FunctionEvaluator.php @@ -100,6 +100,10 @@ public static function evaluate( return self::sqlCurDate($expr); case 'WEEKDAY': return self::sqlWeekDay($conn, $scope, $expr, $row, $result); + case 'INET_ATON': + return self::sqlInetAton($conn, $scope, $expr, $row, $result); + case 'INET_NTOA': + return self::sqlInetNtoa($conn, $scope, $expr, $row, $result); } throw new ProcessorException("Function " . $expr->functionName . " not implemented yet"); @@ -251,6 +255,9 @@ public static function getColumnSchema( return Evaluator::getColumnSchema($expr->args[0], $scope, $columns); + case 'INET_ATON': + return new Column\IntColumn(true, 15); + case 'DATEDIFF': case 'DAY': case 'WEEKDAY': @@ -1280,6 +1287,68 @@ private static function sqlRound( return \round($number, $precision); } + /** + * @param array $row + * @return float|null + */ + private static function sqlInetAton( + FakePdoInterface $conn, + Scope $scope, + FunctionExpression $expr, + array $row, + QueryResult $result + ) : ?float { + $args = $expr->args; + + if (\count($args) !== 1) { + throw new ProcessorException("MySQL INET_ATON() function must be called with one argument"); + } + + $subject = Evaluator::evaluate($conn, $scope, $args[0], $row, $result); + + if (!is_string($subject)) { + // INET_ATON() returns NULL if it does not understand its argument. + return null; + } + + $value = ip2long($subject); + + if (!$value) { + return null; + } + + // https://www.php.net/manual/en/function.ip2long.php - this comes as a signed int + //use %u to convert this to an unsigned long, then cast it as a float + return floatval(sprintf('%u', $value)); + } + + /** + * @param array $row + * @return string + */ + private static function sqlInetNtoa( + FakePdoInterface $conn, + Scope $scope, + FunctionExpression $expr, + array $row, + QueryResult $result + ) : ?string { + $args = $expr->args; + + if (\count($args) !== 1) { + throw new ProcessorException("MySQL INET_NTOA() function must be called with one argument"); + } + + $subject = Evaluator::evaluate($conn, $scope, $args[0], $row, $result); + + if (!is_numeric($subject)) { + // INET_NTOA() returns NULL if it does not understand its argument + return null; + } + + return long2ip((int)$subject); + } + private static function getPhpIntervalFromExpression( FakePdoInterface $conn, Scope $scope, diff --git a/tests/EndToEndTest.php b/tests/EndToEndTest.php index c9a449de..a594140e 100644 --- a/tests/EndToEndTest.php +++ b/tests/EndToEndTest.php @@ -542,6 +542,51 @@ public function testDecimalArithhmetic() ); } + public function testInetAtoN() + { + $pdo = self::getPdo('mysql:foo'); + $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + + $query = $pdo->prepare("SELECT INET_ATON('255.255.255.255') AS a, INET_ATON('192.168.1.1') AS b, INET_ATON('127.0.0.1') AS c, INET_ATON('not an ip') AS d, INET_ATON(NULL) as e"); + $query->execute(); + $this->assertSame( + [ + [ + 'a' => 4294967295, + 'b' => 3232235777, + 'c' => 2130706433, + 'd' => NULL, + 'e' => NULL, + ], + ], + $query->fetchAll(\PDO::FETCH_ASSOC) + ); + } + + + public function testInetNtoA() + { + $pdo = self::getPdo('mysql:foo'); + $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + + $query = $pdo->prepare("SELECT INET_NTOA(4294967295) AS a, INET_NTOA(3232235777) AS b, INET_NTOA(2130706433) AS c, INET_NTOA(NULL) as d, INET_NTOA('not a number') as e"); + $query->execute(); + + $this->assertSame( + [ + [ + 'a' => '255.255.255.255', + 'b' => '192.168.1.1', + 'c' => '127.0.0.1', + 'd' => NULL, + 'e' => NULL, + ], + ], + $query->fetchAll(\PDO::FETCH_ASSOC) + ); + } + + public function testRound() { $pdo = self::getPdo('mysql:foo');