From 4af870f55c6a758170b26a7a0eaf76206ffd0f8c Mon Sep 17 00:00:00 2001 From: Mostafa yavari Date: Wed, 1 Jan 2025 13:23:38 +0330 Subject: [PATCH 1/4] add timestamp and maketime functions without tests --- README.md | 115 ++-- .../Expression/FunctionEvaluator.php | 563 ++++++++++++------ tests/SelectParseTest.php | 9 + 3 files changed, 427 insertions(+), 260 deletions(-) diff --git a/README.md b/README.md index d327a0cf..58e6d24f 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,83 @@ # PHP MySQL Engine -PHP MySQL Engine is a library for PHP that allows you to test database-driven applications with an in-memory simulation of MySQL 5.6. This project extends the `PDO` class and allows you to call common PDO MySQL methods. It supports a wide variety of queries, and some PDO-specific functionality like transactions and different fetch modes. +## Overview +PHP MySQL Engine is a library that simulates MySQL 5.6 in-memory for testing database-driven PHP applications. This fork adds new functionality to further enhance testing capabilities while maintaining simplicity and ease of use. -PHP MySQL Engine is based on Slack's [Hack SQL Fake](https://github.com/slackhq/hack-sql-fake) created by [Scott Sandler](https://github.com/ssandler). +This library extends the PDO class, enabling you to call common PDO MySQL methods and execute a wide variety of queries, including SELECT, INSERT, UPDATE, DELETE, and more. -You can read an article about this tool [here](https://medium.com/vimeo-engineering-blog/the-great-pretender-faster-application-tests-with-mysql-simulation-26250f13d251). +## Key Features +- **In-Memory Testing**: Simulates MySQL databases with in-memory data for fast and efficient testing. +- **Wide Query Support**: Supports a range of SQL queries, including JOINs, subqueries, UNIONs, and more. +- **PDO Compatibility**: Implements PDO-specific functionality, including transactions, fetch modes, and prepared statements. +- **Validating Parser**: Helps catch SQL syntax errors during testing. ## Motivation +Testing database-driven applications often involves two traditional approaches: -Currently there are two ways to test code that reads and writes to a database: +1. **Mock SQL Execution**: Fragile and requires significant manual effort. +2. **Using a Real Database**: Performance-heavy and requires careful management of test data. -- Mock SQL query execution
- Mocks require an explicit list of queries that are expected to run and results to return. This leads to significant manual work setting up expectations, and tests which are fragile and must be updated even on benign changes to the code or queries. It also means the data access layer is not unit tested. - -- Use an actual database
- It might make sense to test with a separate database instance – this is what we have done in the past at Vimeo. But databases like MySQL are designed to be filled with lots of long-lasting data, whereas unit tests write small amounts of very short-lived data. This means that extra care has to be taken to make sure that test databases are truncated between tests, which creates a performance issue. +PHP MySQL Engine solves these issues by using PHP arrays to simulate an in-memory database, enabling fast, lightweight, and robust testing for database interactions. -PHP MySQL Engine takes a different approach - it parses and executes `SELECT`, `INSERT`, `UPDATE`, and `DELETE` queries against an in-memory "database" stored in PHP arrays. As long as the amount of data used for testing is small, this solves the problems mentioned above. +## New Additions in This Fork +- MAKETIME +- TIMESTAMP ## SQL Syntax Supported - -This library supports a wide variety of query syntax, including: - -- `FROM`, `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT` clauses supported as appropriate for each query type -- `JOIN` queries with all join types -- multi-queries such as subqueries, `UNION`, `UNION ALL`, `INTERSECT`, `EXCEPT` -- complex expressions such as `CASE`, `BETWEEN`, and row comparators `(1, 2, 3) < (4, 5, 6)` -- all basic operators implemented with operator precedence -- column aliases, cross-database queries -- `INSERT ... ON DUPLICATE KEY UPDATE` -- A variety of SQL functions such as `COUNT(), NULLIF(), COALESCE(), CONCAT_WS()` and many others -- Temporary variables like `@previous_name := user.name` -- Validating parser: the query parser will throw exceptions on most invalid SQL Queries, helping protect your production environment from accidental SQL syntax errors - -## Unsupported MySQL features - -This engine does _not_ support [MySQL Stored objects](https://dev.mysql.com/doc/refman/5.6/en/stored-objects.html), which precludes the testing of stored procedures, triggers and views. - -## Caveat Emptor - -Unlike [Psalm](https://github.com/vimeo/psalm), this package is not designed with a wide audience in mind. For a project to really benefit from this library it should already have a large number of tests that require a database connection to complete, and the project maintainers must understand the tradeoffs associated with using an unofficial MySQL implementation in their test suite. - -## Known issues - -### Result types when not emulating prepares - -By default the engine returns all data formatted as a string. If `$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false)` is called, the engine will instead infer column types (for example, `SUM(some_int_column)` will be given an `int` type). In some cases `php-mysql-engine` may do a better job of inferring correct column types than actual MySQL, which defaults to string when it can’t work out a column type. If you do strict type checks on the results you may see small discrepancies. +The library supports: +- **Query Clauses**: FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. +- **JOINs**: All join types (INNER, LEFT, RIGHT, etc.). +- **Subqueries and Multi-Queries**: UNION, UNION ALL, INTERSECT, EXCEPT. +- **Complex Expressions**: CASE, BETWEEN, row comparators. +- **SQL Functions**: COUNT(), NULLIF(), COALESCE(), CONCAT_WS(), and more. +- **INSERT Features**: INSERT ... ON DUPLICATE KEY UPDATE. +- **Temporary Variables**: e.g., `@var := value`. + +## Unsupported Features +- MySQL Stored Objects: Stored procedures, triggers, and views. ## Installation +Install the library using Composer: -``` -composer require --dev vimeo/php-mysql-engine +```bash +composer require --dev your-namespace/php-mysql-engine ``` ## Usage - -PHP MySQL Engine works by providing a subclass of [PDO](https://www.php.net/manual/en/class.pdo.php). - -You can instantiate the subclass as you would `PDO`, and use dependency injection or similar to provide that instance to your application code. +To use PHP MySQL Engine, instantiate the provided subclass of PDO and inject it into your application: ```php -// use a class specific to your current PHP version (APIs changed in major versions) -$pdo = new \Vimeo\MysqlEngine\Php8\FakePdo($dsn, $user, $password); -// currently supported attributes +$pdo = new \YourNamespace\MysqlEngine\Php8\FakePdo($dsn, $user, $password); $pdo->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER); $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); ``` -The rest of your code can operate as normal, using the database in the same way it is used in production. - -## Why doesn't it support X? +The rest of your application can interact with the database as usual. -This library aims to support everything its users use in MySQL, rather than every possibly feature MySQL offers. We welcome pull requests to add support for new syntax, sql functions, data types, bug fixes, and other features. - -## Why doesn’t this project have an issue tracker? +## Contributing +This library focuses on supporting commonly used MySQL features. Contributions to add new features or syntax are welcome. +Contributions are encouraged to expand functionality or improve the library. To contribute: -Maintaining open-source projects is hard work, and I don't want to make more work for me or my colleagues. Use this project very much use at your own risk. +1. Fork the repository. +2. Add your features or fixes. +3. Ensure all tests pass: -If you want to fork the project with an issue tracker, feel free! + ```bash + vendor/bin/phpunit + vendor/bin/psalm + ``` +4. Submit a pull request. -## Contributing +## Known Issues +- **Result Types**: By default, all data is returned as strings. Setting `\PDO::ATTR_EMULATE_PREPARES` to `false` allows type inference, which may differ slightly from real MySQL behavior. -If you want to create a PR, please make sure it passes unit tests: +## License +This project is licensed under the MIT License. See the LICENSE file for details. -``` -vendor/bin/phpunit -``` +--- -and also Psalm's checks +**Disclaimer:** Use this library at your own risk. It is designed for projects with significant testing needs and maintainers who understand the tradeoffs of using an unofficial MySQL implementation. -``` -vendor/bin/psalm -``` +--- -Thanks! +Thank you for using PHP MySQL Engine! Happy testing! diff --git a/src/Processor/Expression/FunctionEvaluator.php b/src/Processor/Expression/FunctionEvaluator.php index 8294c504..03ea4867 100644 --- a/src/Processor/Expression/FunctionEvaluator.php +++ b/src/Processor/Expression/FunctionEvaluator.php @@ -1,4 +1,5 @@ functionName) { case 'COUNT': @@ -114,20 +115,24 @@ public static function evaluate( return self::sqlInetAton($conn, $scope, $expr, $row, $result); case 'INET_NTOA': return self::sqlInetNtoa($conn, $scope, $expr, $row, $result); + case 'MAKETIME': + return self::sqlMAKETIME($conn, $scope, $expr, $row, $result); + case 'TIMESTAMP': + return self::sqlTimestamp($conn, $scope, $expr, $row, $result); } throw new ProcessorException("Function " . $expr->functionName . " not implemented yet"); } /** - * @param array $columns + * @param array $columns * @return Column */ public static function getColumnSchema( FunctionExpression $expr, - Scope $scope, - array $columns - ) : Column { + Scope $scope, + array $columns + ): Column { switch ($expr->functionName) { case 'COUNT': return new Column\IntColumn(true, 10); @@ -310,10 +315,10 @@ public static function getColumnSchema( * @return int */ private static function sqlCount( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result + QueryResult $result ) { $inner = $expr->getExpr(); @@ -352,10 +357,10 @@ private static function sqlCount( * @return ?numeric */ private static function sqlSum( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result + QueryResult $result ) { $expr = $expr->getExpr(); @@ -370,7 +375,7 @@ private static function sqlSum( throw new \TypeError('Failed assertion'); })(); $val = Evaluator::evaluate($conn, $scope, $expr, $row, $result); - $num = \is_int($val) ? $val : (double) $val; + $num = \is_int($val) ? $val : (double)$val; $sum += $num; } @@ -394,15 +399,15 @@ private static function castAggregate($value, Expression $expr, QueryResult $res if ($column) { switch ($column->getPhpType()) { case 'int': - return (int) $value; + return (int)$value; case 'float': - return (float) $value; + return (float)$value; case 'string': if ($column instanceof \Vimeo\MysqlEngine\Schema\Column\Decimal) { /** @var numeric-string */ - return \number_format((float) $value, $column->getDecimalScale(), '.', ''); + return \number_format((float)$value, $column->getDecimalScale(), '.', ''); } } } @@ -416,10 +421,10 @@ private static function castAggregate($value, Expression $expr, QueryResult $res * @return mixed */ private static function sqlMin( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result + QueryResult $result ) { $expr = $expr->getExpr(); $values = []; @@ -451,10 +456,10 @@ private static function sqlMin( * @return mixed */ private static function sqlMax( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result + QueryResult $result ) { $expr = $expr->getExpr(); $values = []; @@ -487,11 +492,11 @@ private static function sqlMax( * @return mixed */ private static function sqlMod( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -500,9 +505,9 @@ private static function sqlMod( } $n = $args[0]; - $n_value = (int) Evaluator::evaluate($conn, $scope, $n, $row, $result); + $n_value = (int)Evaluator::evaluate($conn, $scope, $n, $row, $result); $m = $args[1]; - $m_value = (int) Evaluator::evaluate($conn, $scope, $m, $row, $result); + $m_value = (int)Evaluator::evaluate($conn, $scope, $m, $row, $result); return $n_value % $m_value; } @@ -513,10 +518,10 @@ private static function sqlMod( * @return mixed */ private static function sqlAvg( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result + QueryResult $result ) { $expr = $expr->getExpr(); $values = []; @@ -548,11 +553,11 @@ private static function sqlAvg( * @return mixed */ private static function sqlIf( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -563,7 +568,7 @@ private static function sqlIf( $condition = $args[0]; $arg_to_evaluate = 2; - if ((bool) Evaluator::evaluate($conn, $scope, $condition, $row, $result)) { + if ((bool)Evaluator::evaluate($conn, $scope, $condition, $row, $result)) { $arg_to_evaluate = 1; } @@ -578,11 +583,11 @@ private static function sqlIf( * @return mixed */ private static function sqlSubstring( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -591,14 +596,14 @@ private static function sqlSubstring( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); $position = $args[1]; - $pos = (int) Evaluator::evaluate($conn, $scope, $position, $row, $result); + $pos = (int)Evaluator::evaluate($conn, $scope, $position, $row, $result); $pos -= 1; $length = $args[2] ?? null; if ($length !== null) { - $len = (int) Evaluator::evaluate($conn, $scope, $length, $row, $result); + $len = (int)Evaluator::evaluate($conn, $scope, $length, $row, $result); return \mb_substr($string, $pos, $len); } @@ -612,11 +617,11 @@ private static function sqlSubstring( * @return mixed */ private static function sqlSubstringIndex( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -625,13 +630,13 @@ private static function sqlSubstringIndex( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); $delimiter = $args[1]; - $delim = (string) Evaluator::evaluate($conn, $scope, $delimiter, $row, $result); + $delim = (string)Evaluator::evaluate($conn, $scope, $delimiter, $row, $result); $pos = $args[2]; if ($pos !== null && $delim !== '') { - $count = (int) Evaluator::evaluate($conn, $scope, $pos, $row, $result); + $count = (int)Evaluator::evaluate($conn, $scope, $pos, $row, $result); $parts = \explode($delim, $string); if ($count < 0) { @@ -653,11 +658,11 @@ private static function sqlSubstringIndex( * @return mixed */ private static function sqlLower( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -666,7 +671,7 @@ private static function sqlLower( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strtolower($string); } @@ -677,11 +682,11 @@ private static function sqlLower( * @return mixed */ private static function sqlUpper( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -690,7 +695,7 @@ private static function sqlUpper( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strtoupper($string); } @@ -700,11 +705,11 @@ private static function sqlUpper( * @return mixed */ private static function sqlLength( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -713,7 +718,7 @@ private static function sqlLength( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strlen($string); } @@ -723,11 +728,11 @@ private static function sqlLength( * @return mixed */ private static function sqlBinary( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -745,11 +750,11 @@ private static function sqlBinary( * @return mixed */ private static function sqlCharLength( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -758,7 +763,7 @@ private static function sqlCharLength( } $subject = $args[0]; - $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \mb_strlen($string); } @@ -769,11 +774,11 @@ private static function sqlCharLength( * @return mixed */ private static function sqlCoalesce( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { if (!\count($expr->args)) { throw new ProcessorException("MySQL COALESCE() function must be called with at least one argument"); @@ -796,11 +801,11 @@ private static function sqlCoalesce( * @return mixed */ private static function sqlGreatest( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -823,11 +828,11 @@ private static function sqlGreatest( * @return mixed */ private static function sqlNullif( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -845,12 +850,12 @@ private static function sqlNullif( * @param array $row */ private static function sqlIsNull( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : int { + array $row, + QueryResult $result + ): int { $args = $expr->args; if (\count($args) !== 1) { @@ -864,12 +869,12 @@ private static function sqlIsNull( * @param array $row */ private static function sqlFromUnixtime( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : string { + array $row, + QueryResult $result + ): string { $args = $expr->args; if (\count($args) !== 1) { @@ -878,19 +883,19 @@ private static function sqlFromUnixtime( $column = Evaluator::evaluate($conn, $scope, $args[0], $row, $result); - return \date('Y-m-d H:i:s', (int) $column); + return \date('Y-m-d H:i:s', (int)$column); } /** * @param array $row */ private static function sqlUnixTimestamp( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?int { + array $row, + QueryResult $result + ): ?int { $args = $expr->args; switch (\count($args)) { @@ -913,11 +918,11 @@ private static function sqlUnixTimestamp( * @return string */ private static function sqlConcat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -927,7 +932,7 @@ private static function sqlConcat( $final_concat = ""; foreach ($args as $arg) { - $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); $final_concat .= $val; } @@ -938,17 +943,17 @@ private static function sqlConcat( * @param array $row */ private static function sqlGroupConcat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ): string { $args = $expr->args; $final_concat = ""; foreach ($args as $arg) { - $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); $final_concat .= $val; } @@ -961,11 +966,11 @@ private static function sqlGroupConcat( * @return string */ private static function sqlConcatWS( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -978,7 +983,7 @@ private static function sqlConcatWS( throw new ProcessorException("MySQL CONCAT_WS() function required non null separator"); } - $separator = (string) $separator; + $separator = (string)$separator; $final_concat = ""; foreach ($args as $k => $arg) { @@ -986,7 +991,7 @@ private static function sqlConcatWS( continue; } - $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); if ($final_concat === '') { $final_concat = $final_concat . $val; @@ -1004,11 +1009,11 @@ private static function sqlConcatWS( * @return mixed */ private static function sqlColumn( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; $num_args = \count($args); @@ -1042,11 +1047,11 @@ private static function sqlColumn( * @return mixed */ private static function sqlValues( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; $num_args = \count($args); @@ -1071,12 +1076,12 @@ private static function sqlValues( * @param array $row */ private static function sqlDate( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?string { + array $row, + QueryResult $result + ): ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1100,12 +1105,12 @@ private static function sqlDate( * @param array $row */ private static function sqlLastDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?string { + array $row, + QueryResult $result + ): ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1139,12 +1144,12 @@ private static function sqlCurDate(FunctionExpression $expr): string * @param array $row */ private static function sqlWeekDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?int { + array $row, + QueryResult $result + ): ?int { $args = $expr->args; if (\count($args) !== 1) { @@ -1170,11 +1175,11 @@ private static function sqlWeekDay( * @return mixed */ private static function sqlDateFormat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -1206,12 +1211,12 @@ private static function sqlDateFormat( * @param array $row */ private static function sqlDateSub( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?string { + array $row, + QueryResult $result + ): ?string { $args = $expr->args; if (\count($args) !== 2) { @@ -1238,7 +1243,7 @@ private static function sqlDateSub( // mimic behaviour of MySQL for leap years and other rollover dates if (($interval->m || $interval->y) - && (int) $first_date->format('d') >=28 + && (int)$first_date->format('d') >= 28 && ($candidate->format('d') !== $first_date->format('d')) ) { // remove a week @@ -1254,12 +1259,12 @@ private static function sqlDateSub( * @param array $row */ private static function sqlDateAdd( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?string { + array $row, + QueryResult $result + ): ?string { $args = $expr->args; if (\count($args) !== 2) { @@ -1286,7 +1291,7 @@ private static function sqlDateAdd( // mimic behaviour of MySQL for leap years and other rollover dates if (($interval->m || $interval->y) - && (int) $first_date->format('d') >= 28 + && (int)$first_date->format('d') >= 28 && ($candidate->format('d') !== $first_date->format('d')) ) { // remove a week @@ -1302,12 +1307,12 @@ private static function sqlDateAdd( * @param array $row */ private static function sqlDateDiff( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : int { + array $row, + QueryResult $result + ): int { $args = $expr->args; if (\count($args) !== 2) { @@ -1324,12 +1329,12 @@ private static function sqlDateDiff( * @param array $row */ private static function sqlDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : int { + array $row, + QueryResult $result + ): int { $args = $expr->args; if (\count($args) !== 1) { @@ -1347,11 +1352,11 @@ private static function sqlDay( * @return float|int */ private static function sqlRound( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -1375,12 +1380,12 @@ private static function sqlRound( * @return float|null */ private static function sqlInetAton( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?float { + array $row, + QueryResult $result + ): ?float { $args = $expr->args; if (\count($args) !== 1) { @@ -1410,12 +1415,12 @@ private static function sqlInetAton( * @return string */ private static function sqlInetNtoa( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) : ?string { + array $row, + QueryResult $result + ): ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1437,11 +1442,11 @@ private static function sqlInetNtoa( * @return float|0 */ private static function sqlCeiling( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -1470,11 +1475,11 @@ private static function sqlCeiling( * @return float|0 */ private static function sqlFloor( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result + array $row, + QueryResult $result ) { $args = $expr->args; @@ -1499,12 +1504,12 @@ private static function sqlFloor( } private static function getPhpIntervalFromExpression( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, IntervalOperatorExpression $expr, - array $row, - QueryResult $result - ) : \DateInterval { + array $row, + QueryResult $result + ): \DateInterval { $number = Evaluator::evaluate($conn, $scope, $expr->number, $row, $result); switch ($expr->unit) { @@ -1533,4 +1538,170 @@ private static function getPhpIntervalFromExpression( throw new ProcessorException('MySQL INTERVAL unit ' . $expr->unit . ' not supported yet'); } } + + /** + * @param FakePdoInterface $conn + * @param Scope $scope + * @param FunctionExpression $expr + * @param array $row + * @param QueryResult $result + * @return mixed + * @throws ProcessorException + */ + private static function sqlMAKETIME( + FakePdoInterface $conn, + Scope $scope, + FunctionExpression $expr, + array $row, + QueryResult $result + ): mixed { + + if (\count($expr->args) != 3) { + throw new ProcessorException("MySQL MAKETIME() function must be called with three argument"); + } + $created_time = ""; + foreach ($expr->args as $index => $arg) { + $value = Evaluator::evaluate($conn, $scope, $arg, $row, $result); + switch ($index) { + case 0: + if ($value > 839) { + throw new ProcessorException("Hour cannot be greater than 839"); + } + break; + case 1: + case 2: + if ($value > 59) { + throw new ProcessorException("Minute or second cannot be greater than 59"); + } + } + + if ($value < 10) { + $value = "0" . $value; + } + + $created_time .= $value . ":"; + } + + return self::castAggregate(\rtrim($created_time, ":"), $expr, $result); + } + + /** + * @param FakePdoInterface $conn + * @param Scope $scope + * @param FunctionExpression $expr + * @param array $row + * @param QueryResult $result + * @return mixed + * @throws ProcessorException + */ + private static function sqlTimestamp( + FakePdoInterface $conn, + Scope $scope, + FunctionExpression $expr, + array $row, + QueryResult $result + ): mixed { + + if (\count($expr->args) > 2 || \count($expr->args) < 1) { + throw new ProcessorException("MySQL TIMESTAMP() function must be called with maximum two argument"); + } + + $value = Evaluator::evaluate($conn, $scope, $expr->args[0], $row, $result); + + $exploded_value = explode(" ", $value); + + $date = $exploded_value[0]; + + $time_str = "00:00:00"; + + if (isset($exploded_value[1])) { + $time = explode(":", $exploded_value[1]); + + if (count($time) < 1 || count($time) > 3) { + throw new ProcessorException("The given time format is incorrect !!!"); + } + + if ($time[0] < 10) { + $time[0] = "0" . $time[0]; + } + + if (isset($time[1])) { + if ($time[1] < 10) { + $time[1] = "0" . $time[1]; + } + } else { + $time[1] = "00"; + } + + if (isset($time[2])) { + if ($time[2] < 10) { + $time[2] = "0" . $time[2]; + } + } else { + $time[2] = "00"; + } + + $time_str = $time[0] . ":" . $time[1] . ":" . $time[2]; + } + + if (!$first_date = \DateTime::createFromFormat("Y-m-d H:i:s", $date . " " . $time_str)) { + throw new ProcessorException("The given datetime format is incorrect !!!"); + }; + + + if (isset($expr->args[1])) { + $value = Evaluator::evaluate($conn, $scope, $expr->args[1], $row, $result); + $given_time = explode(":", $value); + + switch (count($given_time)) { + case 1: + $processed_time = \array_map("strrev", \str_split(\strrev($given_time[0]), 2)); + $interval = new \DateInterval( + 'PT' . (isset($processed_time[2]) ? $processed_time[2] : '0') . 'H' . + (isset($processed_time[1]) ? $processed_time[1] : '0') . 'M' . + $processed_time[0] . 'S' + ); + break; + case 2: + case 3: + if ($given_time[0] < 10) { + $processed_time[0] = "0" . $given_time[0]; + } else { + $processed_time[0] = $given_time[0]; + } + + if (isset($given_time[1])) { + if ($given_time[1] < 10) { + $processed_time[1] = "0" . $given_time[1]; + } else { + $processed_time[1] = $given_time[1]; + } + } else { + $processed_time[1] = "00"; + } + + if (isset($given_time[2])) { + if ($given_time[2] < 10) { + $processed_time[2] = "0" . $given_time[2]; + } else { + $processed_time[2] = $given_time[2]; + } + } else { + $processed_time[2] = "00"; + } + $interval = new \DateInterval( + 'PT' . $processed_time[0] . 'H' . $processed_time[1] . 'M' . $processed_time[2] . 'S' + ); + break; + default: + throw new ProcessorException("The given time format is incorrect !!!"); + } + + $first_date->add($interval); + } + + + return self::castAggregate($first_date->format('Y-m-d H:i:s'), $expr, $result); + } + } diff --git a/tests/SelectParseTest.php b/tests/SelectParseTest.php index e6f4e4a9..d497d280 100644 --- a/tests/SelectParseTest.php +++ b/tests/SelectParseTest.php @@ -316,4 +316,13 @@ public function testBracketedFirstSelect() $select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql); } + + public function testMAKETIMEFunction() + { + // todo develop test + } + public function testTIMESTAMPFunction() + { + // todo develop test + } } From cf3e47f12b944936601cc75dee3abbf9664931c8 Mon Sep 17 00:00:00 2001 From: Mostafa yavari Date: Wed, 1 Jan 2025 13:59:36 +0330 Subject: [PATCH 2/4] The CONCAT function mistakes it for a delimiter when you pass a string to it as input and throws an error. --- src/Parser/ExpressionParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/ExpressionParser.php b/src/Parser/ExpressionParser.php index 9abb2b0b..c489fbed 100644 --- a/src/Parser/ExpressionParser.php +++ b/src/Parser/ExpressionParser.php @@ -141,7 +141,7 @@ private function getListExpression(array $tokens) $arg = $tokens[$pos]; - if ($arg->value === ',') { + if ($arg->value === ',' && $arg->type == "Separator") { if ($needs_comma) { $needs_comma = false; $pos++; From 2d3eff5a2b434ea4fdb4e18184d689c51d7b98b1 Mon Sep 17 00:00:00 2001 From: Mostafa yavari Date: Sat, 4 Jan 2025 21:33:34 +0330 Subject: [PATCH 3/4] revert readme.md and code style changes to proper pr --- README.md | 115 +++++++++------- .../Expression/FunctionEvaluator.php | 130 ++++++++++++------ 2 files changed, 150 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 58e6d24f..218d3913 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,96 @@ # PHP MySQL Engine -## Overview -PHP MySQL Engine is a library that simulates MySQL 5.6 in-memory for testing database-driven PHP applications. This fork adds new functionality to further enhance testing capabilities while maintaining simplicity and ease of use. +PHP MySQL Engine is a library for PHP that allows you to test database-driven applications with an in-memory simulation of MySQL 5.6. This project extends the `PDO` class and allows you to call common PDO MySQL methods. It supports a wide variety of queries, and some PDO-specific functionality like transactions and different fetch modes. -This library extends the PDO class, enabling you to call common PDO MySQL methods and execute a wide variety of queries, including SELECT, INSERT, UPDATE, DELETE, and more. +PHP MySQL Engine is based on Slack's [Hack SQL Fake](https://github.com/slackhq/hack-sql-fake) created by [Scott Sandler](https://github.com/ssandler). -## Key Features -- **In-Memory Testing**: Simulates MySQL databases with in-memory data for fast and efficient testing. -- **Wide Query Support**: Supports a range of SQL queries, including JOINs, subqueries, UNIONs, and more. -- **PDO Compatibility**: Implements PDO-specific functionality, including transactions, fetch modes, and prepared statements. -- **Validating Parser**: Helps catch SQL syntax errors during testing. +You can read an article about this tool [here](https://medium.com/vimeo-engineering-blog/the-great-pretender-faster-application-tests-with-mysql-simulation-26250f13d251). ## Motivation -Testing database-driven applications often involves two traditional approaches: -1. **Mock SQL Execution**: Fragile and requires significant manual effort. -2. **Using a Real Database**: Performance-heavy and requires careful management of test data. +Currently there are two ways to test code that reads and writes to a database: -PHP MySQL Engine solves these issues by using PHP arrays to simulate an in-memory database, enabling fast, lightweight, and robust testing for database interactions. +- Mock SQL query execution
+ Mocks require an explicit list of queries that are expected to run and results to return. This leads to significant manual work setting up expectations, and tests which are fragile and must be updated even on benign changes to the code or queries. It also means the data access layer is not unit tested. -## New Additions in This Fork -- MAKETIME -- TIMESTAMP +- Use an actual database
+ It might make sense to test with a separate database instance – this is what we have done in the past at Vimeo. But databases like MySQL are designed to be filled with lots of long-lasting data, whereas unit tests write small amounts of very short-lived data. This means that extra care has to be taken to make sure that test databases are truncated between tests, which creates a performance issue. + +PHP MySQL Engine takes a different approach - it parses and executes `SELECT`, `INSERT`, `UPDATE`, and `DELETE` queries against an in-memory "database" stored in PHP arrays. As long as the amount of data used for testing is small, this solves the problems mentioned above. ## SQL Syntax Supported -The library supports: -- **Query Clauses**: FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. -- **JOINs**: All join types (INNER, LEFT, RIGHT, etc.). -- **Subqueries and Multi-Queries**: UNION, UNION ALL, INTERSECT, EXCEPT. -- **Complex Expressions**: CASE, BETWEEN, row comparators. -- **SQL Functions**: COUNT(), NULLIF(), COALESCE(), CONCAT_WS(), and more. -- **INSERT Features**: INSERT ... ON DUPLICATE KEY UPDATE. -- **Temporary Variables**: e.g., `@var := value`. - -## Unsupported Features -- MySQL Stored Objects: Stored procedures, triggers, and views. + +This library supports a wide variety of query syntax, including: + +- `FROM`, `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT` clauses supported as appropriate for each query type +- `JOIN` queries with all join types +- multi-queries such as subqueries, `UNION`, `UNION ALL`, `INTERSECT`, `EXCEPT` +- complex expressions such as `CASE`, `BETWEEN`, and row comparators `(1, 2, 3) < (4, 5, 6)` +- all basic operators implemented with operator precedence +- column aliases, cross-database queries +- `INSERT ... ON DUPLICATE KEY UPDATE` +- A variety of SQL functions such as `COUNT(), NULLIF(), COALESCE(), CONCAT_WS()` and many others +- Temporary variables like `@previous_name := user.name` +- Validating parser: the query parser will throw exceptions on most invalid SQL Queries, helping protect your production environment from accidental SQL syntax errors + +## Unsupported MySQL features + +This engine does _not_ support [MySQL Stored objects](https://dev.mysql.com/doc/refman/5.6/en/stored-objects.html), which precludes the testing of stored procedures, triggers and views. + +## Caveat Emptor + +Unlike [Psalm](https://github.com/vimeo/psalm), this package is not designed with a wide audience in mind. For a project to really benefit from this library it should already have a large number of tests that require a database connection to complete, and the project maintainers must understand the tradeoffs associated with using an unofficial MySQL implementation in their test suite. + +## Known issues + +### Result types when not emulating prepares + +By default the engine returns all data formatted as a string. If `$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false)` is called, the engine will instead infer column types (for example, `SUM(some_int_column)` will be given an `int` type). In some cases `php-mysql-engine` may do a better job of inferring correct column types than actual MySQL, which defaults to string when it can’t work out a column type. If you do strict type checks on the results you may see small discrepancies. ## Installation -Install the library using Composer: -```bash -composer require --dev your-namespace/php-mysql-engine +``` +composer require --dev vimeo/php-mysql-engine ``` ## Usage -To use PHP MySQL Engine, instantiate the provided subclass of PDO and inject it into your application: + +PHP MySQL Engine works by providing a subclass of [PDO](https://www.php.net/manual/en/class.pdo.php). + +You can instantiate the subclass as you would `PDO`, and use dependency injection or similar to provide that instance to your application code. ```php -$pdo = new \YourNamespace\MysqlEngine\Php8\FakePdo($dsn, $user, $password); +// use a class specific to your current PHP version (APIs changed in major versions) +$pdo = new \Vimeo\MysqlEngine\Php8\FakePdo($dsn, $user, $password); +// currently supported attributes $pdo->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER); $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); ``` -The rest of your application can interact with the database as usual. +The rest of your code can operate as normal, using the database in the same way it is used in production. -## Contributing -This library focuses on supporting commonly used MySQL features. Contributions to add new features or syntax are welcome. -Contributions are encouraged to expand functionality or improve the library. To contribute: +## Why doesn't it support X? + +This library aims to support everything its users use in MySQL, rather than every possibly feature MySQL offers. We welcome pull requests to add support for new syntax, sql functions, data types, bug fixes, and other features. + +## Why doesn’t this project have an issue tracker? -1. Fork the repository. -2. Add your features or fixes. -3. Ensure all tests pass: +Maintaining open-source projects is hard work, and I don't want to make more work for me or my colleagues. Use this project very much use at your own risk. - ```bash - vendor/bin/phpunit - vendor/bin/psalm - ``` -4. Submit a pull request. +If you want to fork the project with an issue tracker, feel free! -## Known Issues -- **Result Types**: By default, all data is returned as strings. Setting `\PDO::ATTR_EMULATE_PREPARES` to `false` allows type inference, which may differ slightly from real MySQL behavior. +## Contributing -## License -This project is licensed under the MIT License. See the LICENSE file for details. +If you want to create a PR, please make sure it passes unit tests: ---- +``` +vendor/bin/phpunit +``` -**Disclaimer:** Use this library at your own risk. It is designed for projects with significant testing needs and maintainers who understand the tradeoffs of using an unofficial MySQL implementation. +and also Psalm's checks ---- +``` +vendor/bin/psalm +``` -Thank you for using PHP MySQL Engine! Happy testing! +Thanks! \ No newline at end of file diff --git a/src/Processor/Expression/FunctionEvaluator.php b/src/Processor/Expression/FunctionEvaluator.php index 03ea4867..a806be2c 100644 --- a/src/Processor/Expression/FunctionEvaluator.php +++ b/src/Processor/Expression/FunctionEvaluator.php @@ -1,5 +1,4 @@ functionName) { case 'COUNT': return self::sqlCount($conn, $scope, $expr, $result); @@ -132,7 +132,8 @@ public static function getColumnSchema( FunctionExpression $expr, Scope $scope, array $columns - ): Column { + ): Column + { switch ($expr->functionName) { case 'COUNT': return new Column\IntColumn(true, 10); @@ -319,7 +320,8 @@ private static function sqlCount( Scope $scope, FunctionExpression $expr, QueryResult $result - ) { + ) + { $inner = $expr->getExpr(); if ($expr->distinct) { @@ -361,7 +363,8 @@ private static function sqlSum( Scope $scope, FunctionExpression $expr, QueryResult $result - ) { + ) + { $expr = $expr->getExpr(); $sum = 0; @@ -425,7 +428,8 @@ private static function sqlMin( Scope $scope, FunctionExpression $expr, QueryResult $result - ) { + ) + { $expr = $expr->getExpr(); $values = []; @@ -460,7 +464,8 @@ private static function sqlMax( Scope $scope, FunctionExpression $expr, QueryResult $result - ) { + ) + { $expr = $expr->getExpr(); $values = []; @@ -497,7 +502,8 @@ private static function sqlMod( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 2) { @@ -522,7 +528,8 @@ private static function sqlAvg( Scope $scope, FunctionExpression $expr, QueryResult $result - ) { + ) + { $expr = $expr->getExpr(); $values = []; @@ -558,7 +565,8 @@ private static function sqlIf( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 3) { @@ -588,7 +596,8 @@ private static function sqlSubstring( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 2 && \count($args) !== 3) { @@ -622,7 +631,8 @@ private static function sqlSubstringIndex( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 3) { @@ -663,7 +673,8 @@ private static function sqlLower( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -687,7 +698,8 @@ private static function sqlUpper( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -710,7 +722,8 @@ private static function sqlLength( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -733,7 +746,8 @@ private static function sqlBinary( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -755,7 +769,8 @@ private static function sqlCharLength( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -779,7 +794,8 @@ private static function sqlCoalesce( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { if (!\count($expr->args)) { throw new ProcessorException("MySQL COALESCE() function must be called with at least one argument"); } @@ -806,7 +822,8 @@ private static function sqlGreatest( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) < 2) { @@ -833,7 +850,8 @@ private static function sqlNullif( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 2) { @@ -855,7 +873,8 @@ private static function sqlIsNull( FunctionExpression $expr, array $row, QueryResult $result - ): int { + ): int + { $args = $expr->args; if (\count($args) !== 1) { @@ -874,7 +893,8 @@ private static function sqlFromUnixtime( FunctionExpression $expr, array $row, QueryResult $result - ): string { + ): string + { $args = $expr->args; if (\count($args) !== 1) { @@ -895,7 +915,8 @@ private static function sqlUnixTimestamp( FunctionExpression $expr, array $row, QueryResult $result - ): ?int { + ): ?int + { $args = $expr->args; switch (\count($args)) { @@ -923,7 +944,8 @@ private static function sqlConcat( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) < 2) { @@ -948,7 +970,8 @@ private static function sqlGroupConcat( FunctionExpression $expr, array $row, QueryResult $result - ): string { + ): string + { $args = $expr->args; $final_concat = ""; @@ -971,7 +994,8 @@ private static function sqlConcatWS( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) < 2) { @@ -1014,7 +1038,8 @@ private static function sqlColumn( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; $num_args = \count($args); @@ -1052,7 +1077,8 @@ private static function sqlValues( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; $num_args = \count($args); @@ -1081,7 +1107,8 @@ private static function sqlDate( FunctionExpression $expr, array $row, QueryResult $result - ): ?string { + ): ?string + { $args = $expr->args; if (\count($args) !== 1) { @@ -1110,7 +1137,8 @@ private static function sqlLastDay( FunctionExpression $expr, array $row, QueryResult $result - ): ?string { + ): ?string + { $args = $expr->args; if (\count($args) !== 1) { @@ -1149,7 +1177,8 @@ private static function sqlWeekDay( FunctionExpression $expr, array $row, QueryResult $result - ): ?int { + ): ?int + { $args = $expr->args; if (\count($args) !== 1) { @@ -1180,7 +1209,8 @@ private static function sqlDateFormat( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 2) { @@ -1216,7 +1246,8 @@ private static function sqlDateSub( FunctionExpression $expr, array $row, QueryResult $result - ): ?string { + ): ?string + { $args = $expr->args; if (\count($args) !== 2) { @@ -1264,7 +1295,8 @@ private static function sqlDateAdd( FunctionExpression $expr, array $row, QueryResult $result - ): ?string { + ): ?string + { $args = $expr->args; if (\count($args) !== 2) { @@ -1312,7 +1344,8 @@ private static function sqlDateDiff( FunctionExpression $expr, array $row, QueryResult $result - ): int { + ): int + { $args = $expr->args; if (\count($args) !== 2) { @@ -1334,7 +1367,8 @@ private static function sqlDay( FunctionExpression $expr, array $row, QueryResult $result - ): int { + ): int + { $args = $expr->args; if (\count($args) !== 1) { @@ -1357,7 +1391,8 @@ private static function sqlRound( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1 && \count($args) !== 2) { @@ -1385,7 +1420,8 @@ private static function sqlInetAton( FunctionExpression $expr, array $row, QueryResult $result - ): ?float { + ): ?float + { $args = $expr->args; if (\count($args) !== 1) { @@ -1420,7 +1456,8 @@ private static function sqlInetNtoa( FunctionExpression $expr, array $row, QueryResult $result - ): ?string { + ): ?string + { $args = $expr->args; if (\count($args) !== 1) { @@ -1447,7 +1484,8 @@ private static function sqlCeiling( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -1480,7 +1518,8 @@ private static function sqlFloor( FunctionExpression $expr, array $row, QueryResult $result - ) { + ) + { $args = $expr->args; if (\count($args) !== 1) { @@ -1509,7 +1548,8 @@ private static function getPhpIntervalFromExpression( IntervalOperatorExpression $expr, array $row, QueryResult $result - ): \DateInterval { + ): \DateInterval + { $number = Evaluator::evaluate($conn, $scope, $expr->number, $row, $result); switch ($expr->unit) { @@ -1554,7 +1594,8 @@ private static function sqlMAKETIME( FunctionExpression $expr, array $row, QueryResult $result - ): mixed { + ): mixed + { if (\count($expr->args) != 3) { throw new ProcessorException("MySQL MAKETIME() function must be called with three argument"); @@ -1600,7 +1641,8 @@ private static function sqlTimestamp( FunctionExpression $expr, array $row, QueryResult $result - ): mixed { + ): mixed + { if (\count($expr->args) > 2 || \count($expr->args) < 1) { throw new ProcessorException("MySQL TIMESTAMP() function must be called with maximum two argument"); From 2b9f740d87031c434e58a7e71b4c13841f4514a2 Mon Sep 17 00:00:00 2001 From: Mostafa yavari Date: Fri, 10 Jan 2025 14:35:23 +0330 Subject: [PATCH 4/4] revert code style and done test --- .../Expression/FunctionEvaluator.php | 491 ++++++++---------- tests/EndToEndTest.php | 42 ++ tests/SelectParseTest.php | 23 +- 3 files changed, 288 insertions(+), 268 deletions(-) diff --git a/src/Processor/Expression/FunctionEvaluator.php b/src/Processor/Expression/FunctionEvaluator.php index a806be2c..681731ea 100644 --- a/src/Processor/Expression/FunctionEvaluator.php +++ b/src/Processor/Expression/FunctionEvaluator.php @@ -22,13 +22,12 @@ final class FunctionEvaluator * @return mixed */ public static function evaluate( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { switch ($expr->functionName) { case 'COUNT': return self::sqlCount($conn, $scope, $expr, $result); @@ -125,15 +124,14 @@ public static function evaluate( } /** - * @param array $columns + * @param array $columns * @return Column */ public static function getColumnSchema( FunctionExpression $expr, - Scope $scope, - array $columns - ): Column - { + Scope $scope, + array $columns + ) : Column { switch ($expr->functionName) { case 'COUNT': return new Column\IntColumn(true, 10); @@ -316,12 +314,11 @@ public static function getColumnSchema( * @return int */ private static function sqlCount( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result - ) - { + QueryResult $result + ) { $inner = $expr->getExpr(); if ($expr->distinct) { @@ -359,12 +356,11 @@ private static function sqlCount( * @return ?numeric */ private static function sqlSum( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result - ) - { + QueryResult $result + ) { $expr = $expr->getExpr(); $sum = 0; @@ -378,7 +374,7 @@ private static function sqlSum( throw new \TypeError('Failed assertion'); })(); $val = Evaluator::evaluate($conn, $scope, $expr, $row, $result); - $num = \is_int($val) ? $val : (double)$val; + $num = \is_int($val) ? $val : (double) $val; $sum += $num; } @@ -402,15 +398,15 @@ private static function castAggregate($value, Expression $expr, QueryResult $res if ($column) { switch ($column->getPhpType()) { case 'int': - return (int)$value; + return (int) $value; case 'float': - return (float)$value; + return (float) $value; case 'string': if ($column instanceof \Vimeo\MysqlEngine\Schema\Column\Decimal) { /** @var numeric-string */ - return \number_format((float)$value, $column->getDecimalScale(), '.', ''); + return \number_format((float) $value, $column->getDecimalScale(), '.', ''); } } } @@ -424,12 +420,11 @@ private static function castAggregate($value, Expression $expr, QueryResult $res * @return mixed */ private static function sqlMin( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result - ) - { + QueryResult $result + ) { $expr = $expr->getExpr(); $values = []; @@ -460,12 +455,11 @@ private static function sqlMin( * @return mixed */ private static function sqlMax( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result - ) - { + QueryResult $result + ) { $expr = $expr->getExpr(); $values = []; @@ -497,13 +491,12 @@ private static function sqlMax( * @return mixed */ private static function sqlMod( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 2) { @@ -511,9 +504,9 @@ private static function sqlMod( } $n = $args[0]; - $n_value = (int)Evaluator::evaluate($conn, $scope, $n, $row, $result); + $n_value = (int) Evaluator::evaluate($conn, $scope, $n, $row, $result); $m = $args[1]; - $m_value = (int)Evaluator::evaluate($conn, $scope, $m, $row, $result); + $m_value = (int) Evaluator::evaluate($conn, $scope, $m, $row, $result); return $n_value % $m_value; } @@ -524,12 +517,11 @@ private static function sqlMod( * @return mixed */ private static function sqlAvg( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - QueryResult $result - ) - { + QueryResult $result + ) { $expr = $expr->getExpr(); $values = []; @@ -560,13 +552,12 @@ private static function sqlAvg( * @return mixed */ private static function sqlIf( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 3) { @@ -576,7 +567,7 @@ private static function sqlIf( $condition = $args[0]; $arg_to_evaluate = 2; - if ((bool)Evaluator::evaluate($conn, $scope, $condition, $row, $result)) { + if ((bool) Evaluator::evaluate($conn, $scope, $condition, $row, $result)) { $arg_to_evaluate = 1; } @@ -591,13 +582,12 @@ private static function sqlIf( * @return mixed */ private static function sqlSubstring( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 2 && \count($args) !== 3) { @@ -605,14 +595,14 @@ private static function sqlSubstring( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); $position = $args[1]; - $pos = (int)Evaluator::evaluate($conn, $scope, $position, $row, $result); + $pos = (int) Evaluator::evaluate($conn, $scope, $position, $row, $result); $pos -= 1; $length = $args[2] ?? null; if ($length !== null) { - $len = (int)Evaluator::evaluate($conn, $scope, $length, $row, $result); + $len = (int) Evaluator::evaluate($conn, $scope, $length, $row, $result); return \mb_substr($string, $pos, $len); } @@ -626,13 +616,12 @@ private static function sqlSubstring( * @return mixed */ private static function sqlSubstringIndex( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 3) { @@ -640,13 +629,13 @@ private static function sqlSubstringIndex( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); $delimiter = $args[1]; - $delim = (string)Evaluator::evaluate($conn, $scope, $delimiter, $row, $result); + $delim = (string) Evaluator::evaluate($conn, $scope, $delimiter, $row, $result); $pos = $args[2]; if ($pos !== null && $delim !== '') { - $count = (int)Evaluator::evaluate($conn, $scope, $pos, $row, $result); + $count = (int) Evaluator::evaluate($conn, $scope, $pos, $row, $result); $parts = \explode($delim, $string); if ($count < 0) { @@ -668,13 +657,12 @@ private static function sqlSubstringIndex( * @return mixed */ private static function sqlLower( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -682,7 +670,7 @@ private static function sqlLower( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strtolower($string); } @@ -693,13 +681,12 @@ private static function sqlLower( * @return mixed */ private static function sqlUpper( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -707,7 +694,7 @@ private static function sqlUpper( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strtoupper($string); } @@ -717,13 +704,12 @@ private static function sqlUpper( * @return mixed */ private static function sqlLength( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -731,7 +717,7 @@ private static function sqlLength( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \strlen($string); } @@ -741,13 +727,12 @@ private static function sqlLength( * @return mixed */ private static function sqlBinary( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -764,13 +749,12 @@ private static function sqlBinary( * @return mixed */ private static function sqlCharLength( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -778,7 +762,7 @@ private static function sqlCharLength( } $subject = $args[0]; - $string = (string)Evaluator::evaluate($conn, $scope, $subject, $row, $result); + $string = (string) Evaluator::evaluate($conn, $scope, $subject, $row, $result); return \mb_strlen($string); } @@ -789,13 +773,12 @@ private static function sqlCharLength( * @return mixed */ private static function sqlCoalesce( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { if (!\count($expr->args)) { throw new ProcessorException("MySQL COALESCE() function must be called with at least one argument"); } @@ -817,13 +800,12 @@ private static function sqlCoalesce( * @return mixed */ private static function sqlGreatest( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) < 2) { @@ -845,13 +827,12 @@ private static function sqlGreatest( * @return mixed */ private static function sqlNullif( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 2) { @@ -868,13 +849,12 @@ private static function sqlNullif( * @param array $row */ private static function sqlIsNull( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): int - { + array $row, + QueryResult $result + ) : int { $args = $expr->args; if (\count($args) !== 1) { @@ -888,13 +868,12 @@ private static function sqlIsNull( * @param array $row */ private static function sqlFromUnixtime( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): string - { + array $row, + QueryResult $result + ) : string { $args = $expr->args; if (\count($args) !== 1) { @@ -903,20 +882,19 @@ private static function sqlFromUnixtime( $column = Evaluator::evaluate($conn, $scope, $args[0], $row, $result); - return \date('Y-m-d H:i:s', (int)$column); + return \date('Y-m-d H:i:s', (int) $column); } /** * @param array $row */ private static function sqlUnixTimestamp( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?int - { + array $row, + QueryResult $result + ) : ?int { $args = $expr->args; switch (\count($args)) { @@ -939,13 +917,12 @@ private static function sqlUnixTimestamp( * @return string */ private static function sqlConcat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) < 2) { @@ -954,7 +931,7 @@ private static function sqlConcat( $final_concat = ""; foreach ($args as $arg) { - $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); $final_concat .= $val; } @@ -965,18 +942,17 @@ private static function sqlConcat( * @param array $row */ private static function sqlGroupConcat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): string - { + array $row, + QueryResult $result + ): string { $args = $expr->args; $final_concat = ""; foreach ($args as $arg) { - $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); $final_concat .= $val; } @@ -989,13 +965,12 @@ private static function sqlGroupConcat( * @return string */ private static function sqlConcatWS( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) < 2) { @@ -1007,7 +982,7 @@ private static function sqlConcatWS( throw new ProcessorException("MySQL CONCAT_WS() function required non null separator"); } - $separator = (string)$separator; + $separator = (string) $separator; $final_concat = ""; foreach ($args as $k => $arg) { @@ -1015,7 +990,7 @@ private static function sqlConcatWS( continue; } - $val = (string)Evaluator::evaluate($conn, $scope, $arg, $row, $result); + $val = (string) Evaluator::evaluate($conn, $scope, $arg, $row, $result); if ($final_concat === '') { $final_concat = $final_concat . $val; @@ -1033,13 +1008,12 @@ private static function sqlConcatWS( * @return mixed */ private static function sqlColumn( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; $num_args = \count($args); @@ -1072,13 +1046,12 @@ private static function sqlColumn( * @return mixed */ private static function sqlValues( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; $num_args = \count($args); @@ -1102,13 +1075,12 @@ private static function sqlValues( * @param array $row */ private static function sqlDate( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?string - { + array $row, + QueryResult $result + ) : ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1132,13 +1104,12 @@ private static function sqlDate( * @param array $row */ private static function sqlLastDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?string - { + array $row, + QueryResult $result + ) : ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1172,13 +1143,12 @@ private static function sqlCurDate(FunctionExpression $expr): string * @param array $row */ private static function sqlWeekDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?int - { + array $row, + QueryResult $result + ) : ?int { $args = $expr->args; if (\count($args) !== 1) { @@ -1204,13 +1174,12 @@ private static function sqlWeekDay( * @return mixed */ private static function sqlDateFormat( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 2) { @@ -1241,13 +1210,12 @@ private static function sqlDateFormat( * @param array $row */ private static function sqlDateSub( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?string - { + array $row, + QueryResult $result + ) : ?string { $args = $expr->args; if (\count($args) !== 2) { @@ -1274,7 +1242,7 @@ private static function sqlDateSub( // mimic behaviour of MySQL for leap years and other rollover dates if (($interval->m || $interval->y) - && (int)$first_date->format('d') >= 28 + && (int) $first_date->format('d') >=28 && ($candidate->format('d') !== $first_date->format('d')) ) { // remove a week @@ -1290,13 +1258,12 @@ private static function sqlDateSub( * @param array $row */ private static function sqlDateAdd( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?string - { + array $row, + QueryResult $result + ) : ?string { $args = $expr->args; if (\count($args) !== 2) { @@ -1323,7 +1290,7 @@ private static function sqlDateAdd( // mimic behaviour of MySQL for leap years and other rollover dates if (($interval->m || $interval->y) - && (int)$first_date->format('d') >= 28 + && (int) $first_date->format('d') >= 28 && ($candidate->format('d') !== $first_date->format('d')) ) { // remove a week @@ -1339,13 +1306,12 @@ private static function sqlDateAdd( * @param array $row */ private static function sqlDateDiff( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): int - { + array $row, + QueryResult $result + ) : int { $args = $expr->args; if (\count($args) !== 2) { @@ -1362,13 +1328,12 @@ private static function sqlDateDiff( * @param array $row */ private static function sqlDay( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): int - { + array $row, + QueryResult $result + ) : int { $args = $expr->args; if (\count($args) !== 1) { @@ -1386,13 +1351,12 @@ private static function sqlDay( * @return float|int */ private static function sqlRound( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1 && \count($args) !== 2) { @@ -1415,13 +1379,12 @@ private static function sqlRound( * @return float|null */ private static function sqlInetAton( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?float - { + array $row, + QueryResult $result + ) : ?float { $args = $expr->args; if (\count($args) !== 1) { @@ -1451,13 +1414,12 @@ private static function sqlInetAton( * @return string */ private static function sqlInetNtoa( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ): ?string - { + array $row, + QueryResult $result + ) : ?string { $args = $expr->args; if (\count($args) !== 1) { @@ -1479,13 +1441,12 @@ private static function sqlInetNtoa( * @return float|0 */ private static function sqlCeiling( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -1513,13 +1474,12 @@ private static function sqlCeiling( * @return float|0 */ private static function sqlFloor( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, FunctionExpression $expr, - array $row, - QueryResult $result - ) - { + array $row, + QueryResult $result + ) { $args = $expr->args; if (\count($args) !== 1) { @@ -1543,13 +1503,12 @@ private static function sqlFloor( } private static function getPhpIntervalFromExpression( - FakePdoInterface $conn, - Scope $scope, + FakePdoInterface $conn, + Scope $scope, IntervalOperatorExpression $expr, - array $row, - QueryResult $result - ): \DateInterval - { + array $row, + QueryResult $result + ) : \DateInterval { $number = Evaluator::evaluate($conn, $scope, $expr->number, $row, $result); switch ($expr->unit) { @@ -1579,6 +1538,7 @@ private static function getPhpIntervalFromExpression( } } + /** * @param FakePdoInterface $conn * @param Scope $scope @@ -1745,5 +1705,4 @@ private static function sqlTimestamp( return self::castAggregate($first_date->format('Y-m-d H:i:s'), $expr, $result); } - -} +} \ No newline at end of file diff --git a/tests/EndToEndTest.php b/tests/EndToEndTest.php index 45192f99..1efc6339 100644 --- a/tests/EndToEndTest.php +++ b/tests/EndToEndTest.php @@ -1221,4 +1221,46 @@ private static function getConnectionToFullDB(bool $emulate_prepares = true, boo return $pdo; } + + + public function testMakeTimeFunction() + { + $pdo = self::getConnectionToFullDB(false); + + $query = $pdo->prepare('select MAKETIME(12,13,24) as time'); + + $query->execute(); + + $d = mktime(12, 13, 24, 1, 10, 2025); + + $current_date = date("h:i:s", $d); + + $this->assertSame( + [[ + 'time' => $current_date, + ]], + $query->fetchAll(\PDO::FETCH_ASSOC) + ); + } + + + public function testTIMESTAMPFunction() + { + $pdo = self::getConnectionToFullDB(false); + + $query = $pdo->prepare("SELECT TIMESTAMP('2025-01-10','14:10:00') as date"); + + $query->execute(); + + $d = mktime(14, 10, 00, 1, 10, 2025); + + $current_date = date("Y-m-d H:i:s", $d); + + $this->assertSame( + [[ + 'date' => $current_date, + ]], + $query->fetchAll(\PDO::FETCH_ASSOC) + ); + } } diff --git a/tests/SelectParseTest.php b/tests/SelectParseTest.php index d497d280..4680ccc1 100644 --- a/tests/SelectParseTest.php +++ b/tests/SelectParseTest.php @@ -319,10 +319,29 @@ public function testBracketedFirstSelect() public function testMAKETIMEFunction() { - // todo develop test + $sql = "select MAKETIME(12,13,24)"; + + $select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql); + $this->assertInstanceOf(SelectQuery::class, $select_query); + + $sum_function = $select_query->selectExpressions[0]; + + $this->assertTrue(isset($sum_function->args[0])); + $this->assertEquals(12, $sum_function->args[0]->value); } + public function testTIMESTAMPFunction() { - // todo develop test + $sql = "SELECT TIMESTAMP('2025-01-10', '14:10:00')"; + + $select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql); + $this->assertInstanceOf(SelectQuery::class, $select_query); + + $TIMESTAMP_function = $select_query->selectExpressions[0]; + + $this->assertTrue(isset($TIMESTAMP_function->args[0])); + $this->assertTrue(isset($TIMESTAMP_function->args[1])); + $this->assertEquals('2025-01-10', $TIMESTAMP_function->args[0]->value); + $this->assertEquals('14:10:00', $TIMESTAMP_function->args[1]->value); } }