diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php index 485a260a4..ee224c80f 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php @@ -5021,6 +5021,15 @@ private function translate_runtime_function_call( WP_Parser_Node $node ): string } switch ( $child->id ) { + case WP_MySQL_Lexer::IF_SYMBOL: + // CASE also supports SQLite versions before IIF() was added in 3.32.0. + $nodes = $node->get_child_nodes(); + return sprintf( + 'CASE WHEN %s THEN %s ELSE %s END', + $this->translate( $nodes[0] ), + $this->translate( $nodes[1] ), + $this->translate( $nodes[2] ) + ); case WP_MySQL_Lexer::DATABASE_SYMBOL: return $this->quote_sqlite_value( $this->db_name ); case WP_MySQL_Lexer::CURRENT_TIMESTAMP_SYMBOL: diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php index bacc834bc..7b8828e7b 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-pdo-user-defined-functions.php @@ -60,7 +60,6 @@ public static function register_for( $pdo ): self { 'localtime' => 'now', 'localtimestamp' => 'now', 'isnull' => 'isnull', - 'if' => '_if', 'regexp' => 'regexp', 'field' => 'field', 'log' => 'log', @@ -565,21 +564,6 @@ public function isnull( $field ) { return is_null( $field ); } - /** - * Method to emulate MySQL IF() function. - * - * As 'IF' is a reserved word for PHP, function name must be changed. - * - * @param mixed $expression The statement to be evaluated as true or false. - * @param mixed $truthy Statement or value returned if $expression is true. - * @param mixed $falsy Statement or value returned if $expression is false. - * - * @return mixed - */ - public function _if( $expression, $truthy, $falsy ) { - return ( true === $expression ) ? $truthy : $falsy; - } - /** * Method to emulate MySQL REGEXP() function. * diff --git a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php index 828c2218d..ab8b70a44 100644 --- a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php @@ -12927,6 +12927,105 @@ public function testUpdateErrorsInNonStrictMode(): void { ); } + /** + * @dataProvider ifFunctionConditions + */ + public function testIfFunctionConditions( string $condition, string $expected ): void { + $result = $this->assertQuery( "SELECT IF($condition, 'yes', 'no') AS result" ); + $this->assertSame( $expected, $result[0]->result ); + } + + public function ifFunctionConditions(): array { + return array( + 'null' => array( 'NULL', 'no' ), + 'true' => array( 'TRUE', 'yes' ), + 'false' => array( 'FALSE', 'no' ), + 'true comparison' => array( '1 = 1', 'yes' ), + 'false comparison' => array( '1 = 2', 'no' ), + 'string comparison' => array( "'a' = 'a'", 'yes' ), + 'null comparison' => array( '1 = NULL', 'no' ), + 'zero' => array( '0', 'no' ), + 'one' => array( '1', 'yes' ), + 'positive integer' => array( '2', 'yes' ), + 'negative integer' => array( '-1', 'yes' ), + 'positive fraction' => array( '0.5', 'yes' ), + 'negative fraction' => array( '-0.5', 'yes' ), + 'zero fraction' => array( '0.0', 'no' ), + 'empty string' => array( "''", 'no' ), + 'whitespace' => array( "' '", 'no' ), + 'nonnumeric string' => array( "'abc'", 'no' ), + 'zero string' => array( "'0'", 'no' ), + 'zero fraction string' => array( "'0.0'", 'no' ), + 'one string' => array( "'1'", 'yes' ), + 'positive string' => array( "'2'", 'yes' ), + 'negative string' => array( "'-2'", 'yes' ), + 'fraction string' => array( "'0.5'", 'yes' ), + 'numeric prefix' => array( "'2abc'", 'yes' ), + 'zero prefix' => array( "'0abc'", 'no' ), + 'nonnumeric prefix' => array( "'abc2'", 'no' ), + 'fraction prefix' => array( "'0.5abc'", 'yes' ), + 'leading whitespace' => array( "' -2abc'", 'yes' ), + 'scientific notation' => array( "'1e-10'", 'yes' ), + 'computed condition' => array( 'ABS(-1)', 'yes' ), + 'parenthesized condition' => array( '(1 + 2) > 2', 'yes' ), + 'subquery condition' => array( '(SELECT 1)', 'yes' ), + ); + } + + /** + * @dataProvider ifFunctionResults + */ + public function testIfFunctionResults( string $expression, ?string $expected ): void { + $result = $this->assertQuery( "SELECT $expression AS result" ); + $this->assertSame( $expected, $result[0]->result ); + } + + public function ifFunctionResults(): array { + return array( + 'null true branch' => array( "IF(1, NULL, 'no')", null ), + 'null false branch' => array( "IF(0, 'yes', NULL)", null ), + 'null branches' => array( 'IF(1, NULL, NULL)', null ), + 'integer result' => array( 'IF(1, 2, 3)', '2' ), + 'large integer' => array( 'IF(1, 9223372036854775807, 0)', '9223372036854775807' ), + 'fraction result' => array( 'IF(0, 0.5, 1.5)', '1.5' ), + 'nul byte' => array( "IF(1, 'a\\0b', 'no')", "a\0b" ), + 'nested calls' => array( "IF(IF(1, 1, 0), IF(0, 'no', 'yes'), 'no')", 'yes' ), + 'arithmetic' => array( '2 * IF(1, 3 + 4, 5) + 1', '15' ), + ); + } + + public function testIfFunctionEvaluatesOnlyTheSelectedBranch(): void { + $calls = array(); + $callback = static function ( $value ) use ( &$calls ) { + $calls[] = $value; + return $value; + }; + if ( $this->sqlite instanceof Pdo\Sqlite ) { + $this->sqlite->createFunction( 'if_test_probe', $callback, 1 ); + } else { + $this->sqlite->sqliteCreateFunction( 'if_test_probe', $callback, 1 ); + } + + $result = $this->assertQuery( 'SELECT IF(if_test_probe(1), if_test_probe(2), if_test_probe(3)) AS result' ); + $this->assertSame( '2', $result[0]->result ); + $this->assertSame( array( 1, 2 ), $calls ); + + $calls = array(); + $result = $this->assertQuery( 'SELECT IF(if_test_probe(0), if_test_probe(2), if_test_probe(3)) AS result' ); + $this->assertSame( '3', $result[0]->result ); + $this->assertSame( array( 0, 3 ), $calls ); + } + + public function testIfFunctionInInsertSelect(): void { + $this->assertQuery( 'CREATE TABLE source (id INT, post_type VARCHAR(20))' ); + $this->assertQuery( "INSERT INTO source VALUES (0, 'page'), (1, 'product')" ); + $this->assertQuery( 'CREATE TABLE target (id INT, post_type VARCHAR(20))' ); + $this->assertQuery( "INSERT INTO target SELECT id, IF(id > 0, post_type, 'post') FROM source" ); + + $result = $this->assertQuery( 'SELECT post_type FROM target ORDER BY id' ); + $this->assertSame( array( 'post', 'product' ), array_column( $result, 'post_type' ) ); + } + public function testVersionFunction(): void { $result = $this->query( 'SELECT VERSION()' ); $this->assertSame( '8.0.38-mysql-on-sqlite-' . SQLITE_DRIVER_VERSION, $result[0]->{'VERSION()'} );