use std::ffi::CStr;
use std::mem::ManuallyDrop;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::process;
use std::ptr::NonNull;
use std::slice;
use libc::{c_char, c_int, c_uchar, c_ulonglong, c_void};
use crate::WriteBatch;
use crate::ffi;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum WalRecordAction {
Continue,
Replace,
Ignore,
StopReplay,
Corrupted,
}
impl WalRecordAction {
fn as_raw(self) -> c_int {
let raw = match self {
WalRecordAction::Continue | WalRecordAction::Replace => {
ffi::rocksdb_wal_filter_continue_processing
}
WalRecordAction::Ignore => ffi::rocksdb_wal_filter_ignore_current_record,
WalRecordAction::StopReplay => ffi::rocksdb_wal_filter_stop_replay,
WalRecordAction::Corrupted => ffi::rocksdb_wal_filter_corrupted_record,
};
raw as c_int
}
}
pub struct ColumnFamilyLogNumbers<'a> {
ids: &'a [u32],
log_numbers: &'a [u64],
names: &'a [*const c_char],
name_lengths: &'a [usize],
name_ids: &'a [u32],
}
impl<'a> ColumnFamilyLogNumbers<'a> {
pub fn log_numbers(&self) -> impl Iterator<Item = (u32, u64)> + '_ {
self.ids
.iter()
.copied()
.zip(self.log_numbers.iter().copied())
}
pub fn log_number(&self, cf_id: u32) -> Option<u64> {
let at = self.ids.iter().position(|id| *id == cf_id)?;
self.log_numbers.get(at).copied()
}
pub fn names(&self) -> impl Iterator<Item = (&'a [u8], u32)> + '_ {
let lengths = self.name_lengths.iter().copied();
let ids = self.name_ids.iter().copied();
self.names
.iter()
.zip(lengths)
.zip(ids)
.map(|((name, len), id)| (unsafe { borrowed_slice(name.cast::<u8>(), len) }, id))
}
pub fn id(&self, name: &[u8]) -> Option<u32> {
self.names()
.find_map(|(candidate, id)| (candidate == name).then_some(id))
}
}
pub trait WalFilter: Send + Sync {
fn name(&self) -> &CStr;
fn log_record_found(
&self,
log_number: u64,
log_file_name: &[u8],
batch: &WriteBatch,
replacement: &mut WriteBatch,
) -> WalRecordAction;
fn column_family_log_number_map(&self, _cf_log_numbers: &ColumnFamilyLogNumbers<'_>) {}
}
unsafe fn borrowed_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
if len == 0 {
&[]
} else {
unsafe { slice::from_raw_parts(ptr, len) }
}
}
unsafe fn borrowed_batch(inner: *mut ffi::rocksdb_writebatch_t) -> ManuallyDrop<WriteBatch> {
ManuallyDrop::new(WriteBatch { inner })
}
unsafe extern "C" fn destructor_callback<F: WalFilter>(state: *mut c_void) {
unsafe {
drop(Box::from_raw(state.cast::<F>()));
}
}
unsafe extern "C" fn name_callback<F: WalFilter>(state: *mut c_void) -> *const c_char {
let filter = unsafe { &*state.cast::<F>() };
let name = catch_unwind(AssertUnwindSafe(|| filter.name().as_ptr()));
let Ok(name) = name else { process::abort() };
name
}
unsafe extern "C" fn log_record_found_callback<F: WalFilter>(
state: *mut c_void,
log_number: c_ulonglong,
log_file_name: *const c_char,
log_file_name_len: usize,
batch: *const ffi::rocksdb_writebatch_t,
new_batch: *mut ffi::rocksdb_writebatch_t,
batch_changed: *mut c_uchar,
) -> c_int {
let filter = unsafe { &*state.cast::<F>() };
let file_name = unsafe { borrowed_slice(log_file_name.cast::<u8>(), log_file_name_len) };
let existing = unsafe { borrowed_batch(batch.cast_mut()) };
let mut replacement = unsafe { borrowed_batch(new_batch) };
let action = catch_unwind(AssertUnwindSafe(|| {
filter.log_record_found(log_number, file_name, &existing, &mut replacement)
}));
let Ok(action) = action else { process::abort() };
unsafe {
*batch_changed = u8::from(action == WalRecordAction::Replace);
}
action.as_raw()
}
unsafe extern "C" fn column_family_log_number_map_callback<F: WalFilter>(
state: *mut c_void,
column_family_ids: *const u32,
log_numbers: *const u64,
column_family_log_number_count: usize,
column_family_names: *const *const c_char,
column_family_name_lengths: *const usize,
column_family_name_ids: *const u32,
column_family_name_count: usize,
) {
let filter = unsafe { &*state.cast::<F>() };
let cf_log_numbers = unsafe {
ColumnFamilyLogNumbers {
ids: borrowed_slice(column_family_ids, column_family_log_number_count),
log_numbers: borrowed_slice(log_numbers, column_family_log_number_count),
names: borrowed_slice(column_family_names, column_family_name_count),
name_lengths: borrowed_slice(column_family_name_lengths, column_family_name_count),
name_ids: borrowed_slice(column_family_name_ids, column_family_name_count),
}
};
if catch_unwind(AssertUnwindSafe(|| {
filter.column_family_log_number_map(&cf_log_numbers);
}))
.is_err()
{
process::abort();
}
}
pub(crate) struct OwnedWalFilter {
inner: NonNull<ffi::rocksdb_walfilter_t>,
}
impl OwnedWalFilter {
pub(crate) fn as_ptr(&self) -> *mut ffi::rocksdb_walfilter_t {
self.inner.as_ptr()
}
}
impl Drop for OwnedWalFilter {
fn drop(&mut self) {
unsafe {
ffi::rocksdb_walfilter_destroy(self.inner.as_ptr());
}
}
}
unsafe impl Send for OwnedWalFilter {}
unsafe impl Sync for OwnedWalFilter {}
pub(crate) fn new_wal_filter<F: WalFilter + 'static>(filter: F) -> OwnedWalFilter {
let state = Box::into_raw(Box::new(filter)).cast::<c_void>();
let inner = unsafe {
ffi::rocksdb_walfilter_create(
state,
Some(destructor_callback::<F>),
Some(column_family_log_number_map_callback::<F>),
Some(log_record_found_callback::<F>),
Some(name_callback::<F>),
)
};
OwnedWalFilter {
inner: NonNull::new(inner).expect("rocksdb_walfilter_create returned null"),
}
}