Skip to content

Implement deterministic RAND(N) and improve unseeded RAND() translation#341

Closed
wp-fuse wants to merge 2 commits into
WordPress:trunkfrom
wp-fuse:fix/rand-implementation
Closed

Implement deterministic RAND(N) and improve unseeded RAND() translation#341
wp-fuse wants to merge 2 commits into
WordPress:trunkfrom
wp-fuse:fix/rand-implementation

Conversation

@wp-fuse
Copy link
Copy Markdown
Contributor

@wp-fuse wp-fuse commented Mar 30, 2026

Summary

This PR improves the MySQL-compatible RAND() support in the AST-based SQLite driver. It replaces the previous generic implementation with a bit-exact MySQL Linear Congruential Generator (LCG) and a robust SQLite-level translation for unseeded calls.

Technical Changes

  1. Seeded RAND(N):
    • Refactored the rand() UDF in WP_SQLite_PDO_User_Defined_Functions to use an instance-local LCG.
    • Implemented MySQL’s specific 30-bit seed initialization constants (0x10001, 55555555, 0x10000001) to ensure bit-exact deterministic output for given seeds, as requested in the previous review.
    • Removed mt_srand() and mt_rand() usage to prevent PHP global state pollution.
  2. Unseeded RAND():
    • Updated the AST node translation in WP_PDO_MySQL_On_SQLite to use:
      ((RANDOM() & 0x7FFFFFFFFFFFFFFF) / 9223372036854775808.0).
    • This bitwise approach avoids integer overflow issues inherent to ABS(-9223372036854775808) in SQLite and ensures an unbiased distribution in the [0, 1) range.
  3. Cleanup: This is a standalone PR following the feedback to separate the RAND() fix from other compatibility layers. All string-replacement hacks and non-AST layers have been excluded.

Validation

  • PHPUnit: All 795 tests in the official test suite are passing.
  • CS: Verified against WordPress Coding Standards via phpcs.

@JanJakes
Copy link
Copy Markdown
Member

Thanks for drafting this! I went deeper into a review and realized that it needs multiple changes, so I created #363. Most notably, it needed extensive test coverage, and there were unhandled cases that I addressed in that PR.

@JanJakes JanJakes closed this Apr 17, 2026
JanJakes added a commit that referenced this pull request May 4, 2026
## Summary

Replaces the old `mt_rand(0, 1)` stub — which returned an integer 0 or
1, not a float in `[0, 1)` as MySQL requires — with MySQL-compatible
`RAND()` behavior.

Replaces #341.

## Unseeded `RAND()`

Compiles to a native SQLite expression:

```sql
((RANDOM() & 0x001FFFFFFFFFFFFF) / 9007199254740992.0)
```

The 53-bit mask matches the IEEE 754 double mantissa, so division by
2^53 is exact and strictly less than 1.0. Matches MySQL, where unseeded
`RAND()` uses a thread-level state independent of `RAND(N)`.

## Seeded `RAND(N)`

Routes through a PHP UDF implementing MySQL's exact LCG from
`my_rnd_init()` / `my_rnd()` (`sql/item_func.cc`, `mysys/my_rnd.cc`),
bit-exact against MySQL 9.6. Requires 64-bit PHP.

Seed handling matches `val_int()`: `NULL` becomes 0, floats round to
nearest (`RAND(3.9) == RAND(4)`), numeric strings follow the same path.

## Known divergences

- MySQL distinguishes constant vs non-constant seeds at parse time
(constant = init once per statement, non-constant = reinit per row). A
SQLite UDF can't see the expression, so we approximate by reinitializing
only when the seed value changes. This diverges when a non-constant
expression yields a stable value.
- The UDF keeps one LCG state per connection, so multiple `RAND(N)` call
sites in one query share a stream: `SELECT RAND(1), RAND(1)` returns
`(v1, v2)` here vs `(v, v)` in MySQL.

Both are documented in the `rand()` docblock.

## Metadata

`RAND()` column metadata is now reported as `DOUBLE` / `PARAM_STR`,
removing a long-standing TODO.

## Test plan
- [ ] CI green.
- [ ] Bit-exact reference values against MySQL 9.6 (seeds 0, 1, 3, 5,
and multi-row sequences).
- [ ] NULL, float, numeric/non-numeric string, and negative seed
handling.
- [ ] `RAND()` in `WHERE`, `UPDATE`, `INSERT`, `ORDER BY` (`LIMIT` +
seeded deterministic permutation).
- [ ] Per-statement flush contract inside a transaction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants