mod util;
use pretty_assertions::assert_eq;
use rust_rocksdb::{
CompactOptions, DB, Options, ReadOptions, WaitForCompactOptions, WriteOptions,
properties::num_files_at_level,
};
use util::DBPath;
fn level_layout(db: &DB, max_levels: usize) -> (usize, Vec<u64>) {
let counts: Vec<u64> = (0..max_levels)
.map(|lvl| {
db.property_int_value(num_files_at_level(lvl))
.unwrap()
.unwrap_or(0)
})
.collect();
let non_empty = counts.iter().filter(|&&n| n > 0).count();
(non_empty, counts)
}
#[test]
fn built_with_coroutines_matches_feature_flag() {
assert_eq!(
rust_rocksdb::built_with_coroutines(),
cfg!(feature = "coroutines"),
"built_with_coroutines() must match the cargo feature flag"
);
}
#[test]
fn multi_get_async_io_matches_serial_get_across_levels() {
let path = DBPath::new("_rust_rocksdb_multi_get_async_io_multi_level");
{
let mut opts = Options::default();
opts.create_if_missing(true);
opts.set_write_buffer_size(4 * 1024);
opts.set_target_file_size_base(4 * 1024);
opts.set_max_bytes_for_level_base(16 * 1024);
opts.set_max_bytes_for_level_multiplier(2.0);
opts.set_num_levels(5);
opts.set_disable_auto_compactions(true);
opts.set_level_zero_file_num_compaction_trigger(64);
let db = DB::open(&opts, &path).unwrap();
let mut wo = WriteOptions::default();
wo.set_sync(false);
let value = vec![b'v'; 200];
let make_wait_opts = || {
let mut o = WaitForCompactOptions::default();
o.set_flush(true);
o.set_timeout(30 * 1_000_000); o
};
let put_batch = |start: u32, end: u32| {
for i in start..end {
let key = format!("key-{i:08}");
db.put_opt(key.as_bytes(), &value, &wo).unwrap();
}
db.flush().unwrap();
};
put_batch(0, 200);
let mut co = CompactOptions::default();
co.set_change_level(true);
co.set_target_level(2);
db.compact_range_opt::<&[u8], &[u8]>(None, None, &co);
db.wait_for_compact(&make_wait_opts()).unwrap();
put_batch(200, 400);
let mut co = CompactOptions::default();
co.set_change_level(true);
co.set_target_level(1);
db.compact_range_opt::<&[u8], &[u8]>(None, None, &co);
db.wait_for_compact(&make_wait_opts()).unwrap();
put_batch(400, 600);
db.wait_for_compact(&make_wait_opts()).unwrap();
let (non_empty, counts) = level_layout(&db, 5);
assert!(
non_empty >= 2,
"test setup failed to spread data across multiple LSM levels: \
non_empty={non_empty}, counts={counts:?}; \
coroutines feature: {}",
cfg!(feature = "coroutines")
);
let mut keys: Vec<Vec<u8>> = (0..600u32)
.step_by(37)
.map(|i| format!("key-{i:08}").into_bytes())
.collect();
keys.push(b"key-99999999".to_vec()); keys.push(b"key-00001234".to_vec());
let reference: Vec<Option<Vec<u8>>> = keys.iter().map(|k| db.get(k).unwrap()).collect();
let mut ro = ReadOptions::default();
ro.set_async_io(true);
let key_refs: Vec<&[u8]> = keys.iter().map(Vec::as_slice).collect();
let async_results: Vec<Option<Vec<u8>>> = db
.multi_get_opt(key_refs, &ro)
.into_iter()
.map(Result::unwrap)
.collect();
assert_eq!(
reference,
async_results,
"async_io MultiGet results must match a serial Get loop \
(coroutines feature: {}, level counts: {counts:?})",
cfg!(feature = "coroutines")
);
}
}
#[test]
fn multi_get_async_io_matches_serial_get_single_level() {
let path = DBPath::new("_rust_rocksdb_multi_get_async_io_single_level");
{
let mut opts = Options::default();
opts.create_if_missing(true);
let db = DB::open(&opts, &path).unwrap();
for i in 0..32u32 {
let key = format!("key-{i:04}");
db.put(key.as_bytes(), format!("value-{i}").as_bytes())
.unwrap();
}
db.flush().unwrap();
let keys: Vec<Vec<u8>> = (0..32u32)
.map(|i| format!("key-{i:04}").into_bytes())
.collect();
let reference: Vec<Option<Vec<u8>>> = keys.iter().map(|k| db.get(k).unwrap()).collect();
let mut ro = ReadOptions::default();
ro.set_async_io(true);
let key_refs: Vec<&[u8]> = keys.iter().map(Vec::as_slice).collect();
let async_results: Vec<Option<Vec<u8>>> = db
.multi_get_opt(key_refs, &ro)
.into_iter()
.map(Result::unwrap)
.collect();
assert_eq!(reference, async_results);
}
}