Skip to content

Commit 3aeb602

Browse files
committed
Implement deterministic RAND(N) and improve unseeded RAND() translation
1 parent 783790c commit 3aeb602

2 files changed

Lines changed: 54 additions & 2 deletions

File tree

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4366,6 +4366,18 @@ private function translate_function_call( WP_Parser_Node $node ): string {
43664366
}
43674367

43684368
switch ( $name ) {
4369+
case 'RAND':
4370+
if ( empty( $args ) ) {
4371+
/*
4372+
* SQLite's RANDOM() returns a value between -9223372036854775808 and +9223372036854775807.
4373+
* We clear the sign bit (using & 0x7FFFFFFFFFFFFFFF) to get a positive integer,
4374+
* then divide by 9223372036854775808.0 to get a float between 0.0 and 1.0.
4375+
* We avoid ABS() because ABS(-9223372036854775808) would overflow.
4376+
*/
4377+
return '((RANDOM() & 0x7FFFFFFFFFFFFFFF) / 9223372036854775808.0)';
4378+
}
4379+
// Seeded RAND() calls should be handled by the PHP UDF.
4380+
return $this->translate_sequence( $node->get_children() );
43694381
case 'DATE_FORMAT':
43704382
list ( $date, $mysql_format ) = $args;
43714383

wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ public static function register_for( $pdo ): self {
9595
'_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern',
9696
);
9797

98+
/**
99+
* Seed instances used for the LCG pseudo-random generator (RAND).
100+
*
101+
* @var int|null
102+
*/
103+
private $rand_seed1 = null;
104+
private $rand_seed2 = null;
105+
98106
/**
99107
* A helper function to throw an error from SQLite expressions.
100108
*
@@ -178,8 +186,40 @@ public function md5( $field ) {
178186
*
179187
* @return int
180188
*/
181-
public function rand() {
182-
return mt_rand( 0, 1 );
189+
public function rand( $seed = null ) {
190+
$max_value = 0x3FFFFFFF; // 1073741823
191+
192+
if ( null !== $seed ) {
193+
/*
194+
* Initialize MySQL's internal 30-bit seeds.
195+
* These constants match MySQL's my_rnd_init() implementation.
196+
*/
197+
$n = (int) $seed;
198+
$this->rand_seed1 = ( $n * 0x10001 + 55555555 ) % $max_value;
199+
$this->rand_seed2 = ( $n * 0x10000001 ) % $max_value;
200+
201+
// Ensure seeds are positive.
202+
if ( $this->rand_seed1 < 0 ) {
203+
$this->rand_seed1 += $max_value;
204+
}
205+
if ( $this->rand_seed2 < 0 ) {
206+
$this->rand_seed2 += $max_value;
207+
}
208+
}
209+
210+
if ( null !== $this->rand_seed1 && null !== $this->rand_seed2 ) {
211+
/*
212+
* MySQL's LCG (Linear Congruential Generator) recurrence:
213+
* seed1 = (seed1 * 3 + seed2) % 0x3FFFFFFF
214+
* seed2 = (seed1 + seed2 + 33) % 0x3FFFFFFF
215+
*/
216+
$this->rand_seed1 = ( $this->rand_seed1 * 3 + $this->rand_seed2 ) % $max_value;
217+
$this->rand_seed2 = ( $this->rand_seed1 + $this->rand_seed2 + 33 ) % $max_value;
218+
219+
return (float) $this->rand_seed1 / (float) $max_value;
220+
}
221+
222+
return mt_rand( 0, mt_getrandmax() ) / mt_getrandmax();
183223
}
184224

185225
/**

0 commit comments

Comments
 (0)