From 8501bc930affb1325063360475afbf7bc93a462f Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Sun, 5 Oct 2025 17:28:00 -0300 Subject: [PATCH 1/3] use workspace rand version --- Cargo.lock | 2 +- core/Cargo.toml | 3 +- core/mvcc/persistent_storage/logical_log.rs | 4 +-- core/storage/btree.rs | 4 +-- core/storage/encryption.rs | 38 ++++++++++----------- core/storage/slot_bitmap.rs | 16 +++++---- core/vdbe/execute.rs | 10 +++--- 7 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccd556376..9fbc73fc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4915,7 +4915,7 @@ dependencies = [ "pprof", "quickcheck", "quickcheck_macros", - "rand 0.8.5", + "rand 0.9.2", "rand_chacha 0.9.0", "regex", "regex-syntax", diff --git a/core/Cargo.toml b/core/Cargo.toml index c9c9c54b8..d485df277 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -59,7 +59,7 @@ regex-syntax = { workspace = true, default-features = false, features = [ ] } chrono = { workspace = true, default-features = false, features = ["clock"] } julian_day_converter = "0.4.5" -rand = "0.8.5" +rand = { workspace = true } libm = "0.2" turso_macros = { workspace = true } miette = { workspace = true } @@ -103,7 +103,6 @@ rstest = "0.18.2" rusqlite = { workspace = true, features = ["series"] } quickcheck = { version = "1.0", default-features = false } quickcheck_macros = { version = "1.0", default-features = false } -rand = "0.8.5" # Required for quickcheck rand_chacha = { workspace = true } env_logger = { workspace = true } test-log = { version = "0.2.17", features = ["trace"] } diff --git a/core/mvcc/persistent_storage/logical_log.rs b/core/mvcc/persistent_storage/logical_log.rs index f2a09230e..48b97b2e3 100644 --- a/core/mvcc/persistent_storage/logical_log.rs +++ b/core/mvcc/persistent_storage/logical_log.rs @@ -485,7 +485,7 @@ impl StreamingLogicalLogReader { mod tests { use std::{collections::HashSet, sync::Arc}; - use rand::{thread_rng, Rng}; + use rand::{rng, Rng}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha8Rng, @@ -646,7 +646,7 @@ mod tests { #[test] fn test_logical_log_read_fuzz() { - let seed = thread_rng().gen(); + let seed = rng().random(); let mut rng = ChaCha8Rng::seed_from_u64(seed); let num_transactions = rng.next_u64() % 128; let mut txns = vec![]; diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 65659c4b3..d403c401a 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -7875,7 +7875,7 @@ fn shift_pointers_left(page: &mut PageContent, cell_idx: usize) { #[cfg(test)] mod tests { - use rand::{thread_rng, Rng}; + use rand::{rng, Rng}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha8Rng, @@ -9719,7 +9719,7 @@ mod tests { let mut cells = Vec::new(); let usable_space = 4096; let mut i = 100000; - let seed = thread_rng().gen(); + let seed = rng().random(); tracing::info!("seed {}", seed); let mut rng = ChaCha8Rng::seed_from_u64(seed); while i > 0 { diff --git a/core/storage/encryption.rs b/core/storage/encryption.rs index 3a1bb97b9..156d17c0b 100644 --- a/core/storage/encryption.rs +++ b/core/storage/encryption.rs @@ -979,14 +979,14 @@ mod tests { } fn generate_random_hex_key() -> String { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut bytes = [0u8; 32]; rng.fill(&mut bytes); hex::encode(bytes) } fn generate_random_hex_key_128() -> String { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut bytes = [0u8; 16]; rng.fill(&mut bytes); hex::encode(bytes) @@ -995,7 +995,7 @@ mod tests { fn create_test_page_1() -> Vec { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page[..SQLITE_HEADER.len()].copy_from_slice(SQLITE_HEADER); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); // 48 is the max reserved bytes we might need for metadata with any cipher rng.fill(&mut page[SQLITE_HEADER.len()..DEFAULT_ENCRYPTED_PAGE_SIZE - 48]); page @@ -1135,7 +1135,7 @@ mod tests { #[test] fn test_aes128gcm_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aes128Gcm; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1144,7 +1144,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1165,7 +1165,7 @@ mod tests { #[test] fn test_aes_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aes256Gcm; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1174,7 +1174,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1211,7 +1211,7 @@ mod tests { #[test] fn test_aegis256_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis256; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1220,7 +1220,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1256,7 +1256,7 @@ mod tests { #[test] fn test_aegis128x2_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis128X2; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1265,7 +1265,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1301,7 +1301,7 @@ mod tests { #[test] fn test_aegis128l_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis128L; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1310,7 +1310,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1346,7 +1346,7 @@ mod tests { #[test] fn test_aegis128x4_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis128X4; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1355,7 +1355,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1391,7 +1391,7 @@ mod tests { #[test] fn test_aegis256x2_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis256X2; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1400,7 +1400,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; @@ -1436,7 +1436,7 @@ mod tests { #[test] fn test_aegis256x4_encrypt_decrypt_round_trip() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let cipher_mode = CipherMode::Aegis256X4; let metadata_size = cipher_mode.metadata_size(); let data_size = DEFAULT_ENCRYPTED_PAGE_SIZE - metadata_size; @@ -1445,7 +1445,7 @@ mod tests { let mut page = vec![0u8; DEFAULT_ENCRYPTED_PAGE_SIZE]; page.iter_mut() .take(data_size) - .for_each(|byte| *byte = rng.gen()); + .for_each(|byte| *byte = rng.random()); page }; diff --git a/core/storage/slot_bitmap.rs b/core/storage/slot_bitmap.rs index 140eb708d..86050d3e9 100644 --- a/core/storage/slot_bitmap.rs +++ b/core/storage/slot_bitmap.rs @@ -516,14 +516,14 @@ pub mod tests { ]; for &seed in seeds { let mut rng = StdRng::seed_from_u64(seed); - let n_slots = rng.gen_range(1..10) * 64; + let n_slots = rng.random_range(1..10) * 64; let mut pb = SlotBitmap::new(n_slots); let mut model = vec![true; n_slots as usize]; let iters = 2000usize; for _ in 0..iters { - let op = rng.gen_range(0..100); + let op = rng.random_range(0..100); match op { 0..=49 => { // alloc_one @@ -540,8 +540,9 @@ pub mod tests { } 50..=79 => { // alloc_run with random length - let need = - rng.gen_range(1..=std::cmp::max(1, (n_slots as usize).min(128))) as u32; + let need = rng + .random_range(1..=std::cmp::max(1, (n_slots as usize).min(128))) + as u32; let got = pb.alloc_run(need); if let Some(start) = got { assert!(start + need <= n_slots, "within bounds"); @@ -560,13 +561,14 @@ pub mod tests { } _ => { // free_run on a random valid range - let len = - rng.gen_range(1..=std::cmp::max(1, (n_slots as usize).min(128))) as u32; + let len = rng + .random_range(1..=std::cmp::max(1, (n_slots as usize).min(128))) + as u32; let max_start = n_slots.saturating_sub(len); let start = if max_start == 0 { 0 } else { - rng.gen_range(0..=max_start) + rng.random_range(0..=max_start) }; pb.free_run(start, len); ref_mark_run(&mut model, start, len, true); diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 292657274..6dce1a6e9 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -38,6 +38,7 @@ use crate::{ translate::emitter::TransactionMode, }; use crate::{get_cursor, CheckpointMode, Connection, MvCursor}; +use rand::Rng; use std::env::temp_dir; use std::ops::DerefMut; use std::{ @@ -74,7 +75,7 @@ use super::{ CommitState, }; use parking_lot::RwLock; -use rand::{thread_rng, Rng, RngCore}; +use rand::RngCore; use turso_parser::ast::{self, ForeignKeyClause, Name, SortOrder}; use turso_parser::parser::Parser; @@ -6622,8 +6623,9 @@ pub fn op_new_rowid( // Generate a random i64 and constrain it to the lower half of the rowid range. // We use the lower half (1 to MAX_ROWID/2) because we're in random mode only // when sequential allocation reached MAX_ROWID, meaning the upper range is full. - let mut rng = thread_rng(); - let mut random_rowid: i64 = rng.gen(); + + let mut rng = rand::rng(); + let mut random_rowid: i64 = rng.random(); random_rowid &= MAX_ROWID >> 1; // Mask to keep value in range [0, MAX_ROWID/2] random_rowid += 1; // Ensure positive @@ -8841,7 +8843,7 @@ impl Value { .max(1) as usize; let mut blob: Vec = vec![0; length]; - rand::thread_rng().fill_bytes(&mut blob); + rand::rng().fill_bytes(&mut blob); Value::Blob(blob) } From 8c0b9c6979fd4d7e810357782071d542a4ef955c Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Tue, 21 Oct 2025 11:20:10 -0300 Subject: [PATCH 2/3] add additional `fill_bytes` method to `IO` to deterministically generate random bytes and modify random functions to use them --- Cargo.lock | 1 - core/Cargo.toml | 1 - core/io/mod.rs | 10 +++++++--- core/vdbe/execute.rs | 38 ++++++++++++++++++++++---------------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fbc73fc8..a71426222 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4897,7 +4897,6 @@ dependencies = [ "crossbeam-skiplist", "env_logger 0.11.7", "fallible-iterator", - "getrandom 0.2.15", "hex", "intrusive-collections", "io-uring", diff --git a/core/Cargo.toml b/core/Cargo.toml index d485df277..5616ddf97 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -52,7 +52,6 @@ cfg_block = "0.1.1" fallible-iterator = { workspace = true } hex = { workspace = true } thiserror = { workspace = true } -getrandom = { version = "0.2.15" } regex = { workspace = true } regex-syntax = { workspace = true, default-features = false, features = [ "unicode", diff --git a/core/io/mod.rs b/core/io/mod.rs index ea733f820..1c9d107a4 100644 --- a/core/io/mod.rs +++ b/core/io/mod.rs @@ -3,6 +3,7 @@ use crate::storage::sqlite3_ondisk::WAL_FRAME_HEADER_SIZE; use crate::{BufferPool, Result}; use bitflags::bitflags; use cfg_block::cfg_block; +use rand::{Rng, RngCore}; use std::cell::RefCell; use std::fmt; use std::ptr::NonNull; @@ -147,9 +148,12 @@ pub trait IO: Clock + Send + Sync { } fn generate_random_number(&self) -> i64 { - let mut buf = [0u8; 8]; - getrandom::getrandom(&mut buf).unwrap(); - i64::from_ne_bytes(buf) + rand::rng().random() + } + + /// Fill `dest` with random data. + fn fill_bytes(&self, dest: &mut [u8]) { + rand::rng().fill_bytes(dest); } fn get_memory_io(&self) -> Arc { diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 6dce1a6e9..cd88b78c1 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -38,7 +38,6 @@ use crate::{ translate::emitter::TransactionMode, }; use crate::{get_cursor, CheckpointMode, Connection, MvCursor}; -use rand::Rng; use std::env::temp_dir; use std::ops::DerefMut; use std::{ @@ -75,7 +74,6 @@ use super::{ CommitState, }; use parking_lot::RwLock; -use rand::RngCore; use turso_parser::ast::{self, ForeignKeyClause, Name, SortOrder}; use turso_parser::parser::Parser; @@ -4806,7 +4804,9 @@ pub fn op_function( ScalarFunc::Typeof => Some(reg_value.exec_typeof()), ScalarFunc::Unicode => Some(reg_value.exec_unicode()), ScalarFunc::Quote => Some(reg_value.exec_quote()), - ScalarFunc::RandomBlob => Some(reg_value.exec_randomblob()), + ScalarFunc::RandomBlob => { + Some(reg_value.exec_randomblob(|dest| pager.io.fill_bytes(dest))) + } ScalarFunc::ZeroBlob => Some(reg_value.exec_zeroblob()), ScalarFunc::Soundex => Some(reg_value.exec_soundex()), _ => unreachable!(), @@ -4831,7 +4831,8 @@ pub fn op_function( state.registers[*dest] = Register::Value(result); } ScalarFunc::Random => { - state.registers[*dest] = Register::Value(Value::exec_random()); + state.registers[*dest] = + Register::Value(Value::exec_random(|| pager.io.generate_random_number())); } ScalarFunc::Trim => { let reg_value = &state.registers[*start_reg]; @@ -6623,9 +6624,7 @@ pub fn op_new_rowid( // Generate a random i64 and constrain it to the lower half of the rowid range. // We use the lower half (1 to MAX_ROWID/2) because we're in random mode only // when sequential allocation reached MAX_ROWID, meaning the upper range is full. - - let mut rng = rand::rng(); - let mut random_rowid: i64 = rng.random(); + let mut random_rowid: i64 = pager.io.generate_random_number(); random_rowid &= MAX_ROWID >> 1; // Mask to keep value in range [0, MAX_ROWID/2] random_rowid += 1; // Ensure positive @@ -8826,14 +8825,17 @@ impl Value { }) } - pub fn exec_random() -> Self { - let mut buf = [0u8; 8]; - getrandom::getrandom(&mut buf).unwrap(); - let random_number = i64::from_ne_bytes(buf); - Value::Integer(random_number) + pub fn exec_random(generate_random_number: F) -> Self + where + F: Fn() -> i64, + { + Value::Integer(generate_random_number()) } - pub fn exec_randomblob(&self) -> Value { + pub fn exec_randomblob(&self, fill_bytes: F) -> Value + where + F: Fn(&mut [u8]), + { let length = match self { Value::Integer(i) => *i, Value::Float(f) => *f as i64, @@ -8843,7 +8845,7 @@ impl Value { .max(1) as usize; let mut blob: Vec = vec![0; length]; - rand::rng().fill_bytes(&mut blob); + fill_bytes(&mut blob); Value::Blob(blob) } @@ -10200,6 +10202,8 @@ where #[cfg(test)] mod tests { + use rand::{Rng, RngCore}; + use super::*; use crate::types::Value; @@ -10960,7 +10964,7 @@ mod tests { #[test] fn test_random() { - match Value::exec_random() { + match Value::exec_random(|| rand::rng().random()) { Value::Integer(value) => { // Check that the value is within the range of i64 assert!( @@ -11023,7 +11027,9 @@ mod tests { ]; for test_case in &test_cases { - let result = test_case.input.exec_randomblob(); + let result = test_case.input.exec_randomblob(|dest| { + rand::rng().fill_bytes(dest); + }); match result { Value::Blob(blob) => { assert_eq!(blob.len(), test_case.expected_len); From 72baf48863cffec16e41c281c7c4835fed79cced Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Tue, 21 Oct 2025 11:44:40 -0300 Subject: [PATCH 3/3] add random generation in simulator IO --- simulator/runner/io.rs | 8 ++++++-- simulator/runner/memory/io.rs | 8 ++++++-- whopper/io.rs | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/simulator/runner/io.rs b/simulator/runner/io.rs index c5c38f928..baf4d9e98 100644 --- a/simulator/runner/io.rs +++ b/simulator/runner/io.rs @@ -3,7 +3,7 @@ use std::{ sync::Arc, }; -use rand::{RngCore, SeedableRng}; +use rand::{Rng, RngCore, SeedableRng}; use rand_chacha::ChaCha8Rng; use turso_core::{Clock, IO, Instant, OpenFlags, PlatformIO, Result}; @@ -136,6 +136,10 @@ impl IO for SimulatorIO { } fn generate_random_number(&self) -> i64 { - self.rng.borrow_mut().next_u64() as i64 + self.rng.borrow_mut().random() + } + + fn fill_bytes(&self, dest: &mut [u8]) { + self.rng.borrow_mut().fill_bytes(dest); } } diff --git a/simulator/runner/memory/io.rs b/simulator/runner/memory/io.rs index 975f0d7ce..fc406e7c1 100644 --- a/simulator/runner/memory/io.rs +++ b/simulator/runner/memory/io.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use indexmap::IndexMap; use parking_lot::Mutex; -use rand::{RngCore, SeedableRng}; +use rand::{Rng, RngCore, SeedableRng}; use rand_chacha::ChaCha8Rng; use turso_core::{Clock, Completion, IO, Instant, OpenFlags, Result}; @@ -269,7 +269,11 @@ impl IO for MemorySimIO { } fn generate_random_number(&self) -> i64 { - self.rng.borrow_mut().next_u64() as i64 + self.rng.borrow_mut().random() + } + + fn fill_bytes(&self, dest: &mut [u8]) { + self.rng.borrow_mut().fill_bytes(dest); } fn remove_file(&self, path: &str) -> Result<()> { diff --git a/whopper/io.rs b/whopper/io.rs index 5b9da7b3e..9f8c8a872 100644 --- a/whopper/io.rs +++ b/whopper/io.rs @@ -142,6 +142,11 @@ impl IO for SimulatorIO { let mut rng = self.rng.lock().unwrap(); rng.next_u64() as i64 } + + fn fill_bytes(&self, dest: &mut [u8]) { + let mut rng = self.rng.lock().unwrap(); + rng.fill_bytes(dest); + } } const MAX_FILE_SIZE: usize = 1 << 33; // 8 GiB