From cd4384bc3eaafd018ec067faa7b0247ba79d353d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jakes=CC=8C?= Date: Tue, 27 Oct 2026 13:47:32 +0100 Subject: [PATCH] Pad odd-length hexadecimal literals Add a leading zero to odd-length 0x literals before translating them to SQLite blob literals. Preserve leading zeros and keep rejecting odd-length quoted hexadecimal literals. Cover SELECT, HEX(), INSERT, UPDATE, and literal edge cases. Fixes https://github.com/WordPress/sqlite-database-integration/issues/543 --- .../src/sqlite/class-wp-mysql-on-sqlite.php | 8 +- .../tests/WP_MySQL_On_SQLite_Tests.php | 80 +++++++++++++++++-- .../WP_MySQL_On_SQLite_Translation_Tests.php | 10 +++ 3 files changed, 90 insertions(+), 8 deletions(-) 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 ff92b9480..522b66b52 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 @@ -4541,7 +4541,13 @@ private function translate_token( WP_MySQL_Token $token ): ?string { */ $value = $token->get_value(); if ( '0' === $value[0] && 'x' === $value[1] ) { - return sprintf( "x'%s'", substr( $value, 2 ) ); + $hex = substr( $value, 2 ); + + // MySQL pads odd-length 0x literals with a leading zero. + if ( 1 === strlen( $hex ) % 2 ) { + $hex = '0' . $hex; + } + return sprintf( "x'%s'", $hex ); } return $value; case WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL: 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 63fdb9ea5..aac2728a4 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 @@ -11153,15 +11153,81 @@ public function testBinaryLiterals(): void { $this->assertEquals( array( (object) array( '0b000000001' => pack( 'H*', '0001' ) ) ), $result ); } - public function testHexadecimalLiterals(): void { - $result = $this->assertQuery( 'SELECT 0x417a' ); - $this->assertEquals( array( (object) array( '0x417a' => 'Az' ) ), $result ); + /** + * @dataProvider hexadecimalLiterals + */ + public function testHexadecimalLiterals( string $literal, string $hex ): void { + $result = $this->assertQuery( "SELECT $literal" ); + $this->assertEquals( array( (object) array( $literal => hex2bin( $hex ) ) ), $result ); + + $result = $this->assertQuery( "SELECT HEX($literal) AS value" ); + $this->assertSame( $hex, $result[0]->value ); + } - $result = $this->assertQuery( "SELECT x'417a'" ); - $this->assertEquals( array( (object) array( "x'417a'" => 'Az' ) ), $result ); + public static function hexadecimalLiterals(): array { + return array( + array( '0x0', '00' ), + array( '0x1', '01' ), + array( '0xF', '0F' ), + array( '0x711', '0711' ), + array( '0xaBc', '0ABC' ), + array( '0x0711', '0711' ), + array( '0x00711', '000711' ), + array( '0x000711', '000711' ), + array( '0x000', '0000' ), + array( '0x123456789abcdef01', '0123456789ABCDEF01' ), + array( '0x417a', '417A' ), + array( "x'417a'", '417A' ), + array( "X'417a'", '417A' ), + array( "x''", '' ), + array( "X''", '' ), + ); + } - $result = $this->assertQuery( "SELECT X'417a'" ); - $this->assertEquals( array( (object) array( "X'417a'" => 'Az' ) ), $result ); + public function testOddLengthHexadecimalLiteralsInWrites(): void { + $this->assertQuery( 'CREATE TABLE t (id INT PRIMARY KEY, title TEXT, name VARCHAR(200), data BLOB)' ); + $this->assertQuery( 'INSERT INTO t (id, title, name, data) VALUES (51701, 0x711, 0x711, 0x711)' ); + $this->assertEquals( + array( + (object) array( + 'id' => '51701', + 'title' => '0711', + 'name' => '0711', + 'data' => '0711', + ), + ), + $this->assertQuery( 'SELECT id, HEX(title) AS title, HEX(name) AS name, HEX(data) AS data FROM t' ) + ); + + $this->assertQuery( 'UPDATE t SET title = 0x00711, name = 0x00711, data = 0x00711 WHERE id = 51701' ); + $this->assertEquals( + array( + (object) array( + 'id' => '51701', + 'title' => '000711', + 'name' => '000711', + 'data' => '000711', + ), + ), + $this->assertQuery( 'SELECT id, HEX(title) AS title, HEX(name) AS name, HEX(data) AS data FROM t' ) + ); + } + + /** + * @dataProvider oddLengthQuotedHexadecimalLiterals + */ + public function testOddLengthQuotedHexadecimalLiteralsAreRejected( string $literal ): void { + $this->expectException( WP_MySQL_On_SQLite_Exception::class ); + $this->assertQuery( "SELECT $literal" ); + } + + public static function oddLengthQuotedHexadecimalLiterals(): array { + return array( + array( "x'1'" ), + array( "X'1'" ), + array( "x'711'" ), + array( "X'711'" ), + ); } public function testColumnInfo(): void { diff --git a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Translation_Tests.php b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Translation_Tests.php index e170f3395..d0d522db4 100644 --- a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Translation_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Translation_Tests.php @@ -1742,6 +1742,16 @@ public function testHexadecimalLiterals(): void { "SELECT x'417a' AS `0x417a`", 'SELECT 0x417a' ); + + // Odd-length 0x... values need a leading zero to form full bytes. + $this->assertQuery( + "SELECT x'0711' AS `0x711`", + 'SELECT 0x711' + ); + $this->assertQuery( + "SELECT x'000711' AS `0x00711`", + 'SELECT 0x00711' + ); } public function testSystemVariables(): void {