From 3aeb60235d2a4ce26e519508ca2ac910662d2f0a Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Mar 2026 12:25:57 -0300 Subject: [PATCH 1/2] Implement deterministic RAND(N) and improve unseeded RAND() translation --- .../class-wp-pdo-mysql-on-sqlite.php | 12 +++++ ...s-wp-sqlite-pdo-user-defined-functions.php | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php index b70a0dd6..2588b877 100644 --- a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php +++ b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php @@ -4366,6 +4366,18 @@ private function translate_function_call( WP_Parser_Node $node ): string { } switch ( $name ) { + case 'RAND': + if ( empty( $args ) ) { + /* + * SQLite's RANDOM() returns a value between -9223372036854775808 and +9223372036854775807. + * We clear the sign bit (using & 0x7FFFFFFFFFFFFFFF) to get a positive integer, + * then divide by 9223372036854775808.0 to get a float between 0.0 and 1.0. + * We avoid ABS() because ABS(-9223372036854775808) would overflow. + */ + return '((RANDOM() & 0x7FFFFFFFFFFFFFFF) / 9223372036854775808.0)'; + } + // Seeded RAND() calls should be handled by the PHP UDF. + return $this->translate_sequence( $node->get_children() ); case 'DATE_FORMAT': list ( $date, $mysql_format ) = $args; diff --git a/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php b/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php index b72d787f..6c28aa51 100644 --- a/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php +++ b/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php @@ -95,6 +95,14 @@ public static function register_for( $pdo ): self { '_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern', ); + /** + * Seed instances used for the LCG pseudo-random generator (RAND). + * + * @var int|null + */ + private $rand_seed1 = null; + private $rand_seed2 = null; + /** * A helper function to throw an error from SQLite expressions. * @@ -178,8 +186,40 @@ public function md5( $field ) { * * @return int */ - public function rand() { - return mt_rand( 0, 1 ); + public function rand( $seed = null ) { + $max_value = 0x3FFFFFFF; // 1073741823 + + if ( null !== $seed ) { + /* + * Initialize MySQL's internal 30-bit seeds. + * These constants match MySQL's my_rnd_init() implementation. + */ + $n = (int) $seed; + $this->rand_seed1 = ( $n * 0x10001 + 55555555 ) % $max_value; + $this->rand_seed2 = ( $n * 0x10000001 ) % $max_value; + + // Ensure seeds are positive. + if ( $this->rand_seed1 < 0 ) { + $this->rand_seed1 += $max_value; + } + if ( $this->rand_seed2 < 0 ) { + $this->rand_seed2 += $max_value; + } + } + + if ( null !== $this->rand_seed1 && null !== $this->rand_seed2 ) { + /* + * MySQL's LCG (Linear Congruential Generator) recurrence: + * seed1 = (seed1 * 3 + seed2) % 0x3FFFFFFF + * seed2 = (seed1 + seed2 + 33) % 0x3FFFFFFF + */ + $this->rand_seed1 = ( $this->rand_seed1 * 3 + $this->rand_seed2 ) % $max_value; + $this->rand_seed2 = ( $this->rand_seed1 + $this->rand_seed2 + 33 ) % $max_value; + + return (float) $this->rand_seed1 / (float) $max_value; + } + + return mt_rand( 0, mt_getrandmax() ) / mt_getrandmax(); } /** From a0f7b4709687b71587b56b358c5ab12e36a14525 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Mar 2026 12:56:41 -0300 Subject: [PATCH 2/2] Update RAND() test expectations to use DOUBLE instead of LONGLONG --- tests/WP_SQLite_Driver_Tests.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index c3251708..55d8f6df 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -9068,14 +9068,13 @@ public function testColumnInfoForExpressions(): void { 'mysqli:type' => 8, ), array( - // TODO: Fix custom "RAND()" function to behave like in MySQL. - 'native_type' => 'LONGLONG', // DOUBLE in MySQL. - 'pdo_type' => PDO::PARAM_INT, // PARAM_STR in MySQL. + 'native_type' => 'DOUBLE', + 'pdo_type' => PDO::PARAM_STR, 'flags' => array( 'not_null' ), 'table' => '', 'name' => 'col_expr_19', - 'len' => 21, // 23 in MySQL. - 'precision' => 0, // 31 in MySQL. + 'len' => 23, + 'precision' => 31, 'sqlite:decl_type' => '', // Additional MySQLi metadata. @@ -9084,7 +9083,7 @@ public function testColumnInfoForExpressions(): void { 'mysqli:db' => 'wp', 'mysqli:charsetnr' => 63, 'mysqli:flags' => 0, // 32769 in MySQL. - 'mysqli:type' => 8, // 5 in MySQL. + 'mysqli:type' => 5, ), array( 'native_type' => 'LONGLONG',