use std::{ffi, str::FromStr, time::Duration};
use foundationdb as fdb;
mod raw_bindings {
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
#![allow(dead_code)]
#![allow(missing_docs)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
pub use raw_bindings::{
FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkloadContext, OpaqueWorkload,
};
use raw_bindings::{
FDBMetric, FDBSeverity, FDBSeverity_FDBSeverity_Debug, FDBSeverity_FDBSeverity_Error,
FDBSeverity_FDBSeverity_Info, FDBSeverity_FDBSeverity_Warn, FDBSeverity_FDBSeverity_WarnAlways,
FDBStringPair,
};
pub use raw_bindings::FDBWorkload_FDBWorkload_VT as FDBWorkload_VT;
pub const FDB_WORKLOAD_API_VERSION: i32 = raw_bindings::FDB_WORKLOAD_API_VERSION as i32;
#[doc(hidden)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn str_from_c(c_buf: *const i8) -> String {
let c_str = unsafe { ffi::CStr::from_ptr(c_buf) };
c_str.to_str().unwrap().to_string()
}
#[doc(hidden)]
pub fn str_for_c<T>(buf: T) -> ffi::CString
where
T: Into<Vec<u8>>,
{
ffi::CString::new(buf).unwrap()
}
fn capitalize_first(s: &str) -> Option<String> {
let mut chars = s.chars();
chars
.next()
.map(|first| first.to_uppercase().collect::<String>() + chars.as_str())
}
#[macro_export]
macro_rules! details {
($($k:expr_2021 => $v:expr_2021),* $(,)?) => {
&[
$((
&$k.to_string(), &$v.to_string()
)),*
]
};
}
pub struct WorkloadContext(FDBWorkloadContext);
pub struct Promise(FDBPromise);
pub struct Metrics(FDBMetrics);
#[derive(Clone)]
pub struct Metric<'a> {
pub key: &'a str,
pub val: f64,
pub avg: bool,
pub fmt: Option<&'a str>,
}
#[derive(Clone, Copy)]
#[repr(u32)]
pub enum Severity {
Debug = FDBSeverity_FDBSeverity_Debug,
Info = FDBSeverity_FDBSeverity_Info,
Warn = FDBSeverity_FDBSeverity_Warn,
WarnAlways = FDBSeverity_FDBSeverity_WarnAlways,
Error = FDBSeverity_FDBSeverity_Error,
}
macro_rules! with {
($this:expr_2021=>$method:ident($($args:expr_2021),* $(,)?)) => {
unsafe { (*$this.vt).$method.unwrap_unchecked()($this.inner $(, $args)*) }
};
}
impl WorkloadContext {
#[doc(hidden)]
pub fn new(raw: FDBWorkloadContext) -> Self {
Self(raw)
}
pub fn get_workload_api_version(&self) -> i32 {
self.0.api_version
}
pub fn trace<S, S2, S3>(&self, severity: Severity, name: S, details: &[(S2, S3)])
where
S: Into<Vec<u8>>,
S2: AsRef<str>,
S3: AsRef<str>,
{
let name = str_for_c(name);
let details_storage = details
.iter()
.filter_map(|(key, val)| {
let val = val.as_ref();
if val.is_empty() {
return None;
}
capitalize_first(key.as_ref()).map(|k| (str_for_c(k), str_for_c(val)))
})
.collect::<Vec<_>>();
let details = details_storage
.iter()
.map(|(key, val)| FDBStringPair {
key: key.as_ptr(),
val: val.as_ptr(),
})
.collect::<Vec<_>>();
with! {
self.0 => trace(
severity as FDBSeverity,
name.as_ptr(),
details.as_ptr(),
details.len() as i32,
)
}
}
pub fn get_process_id(&self) -> u64 {
with! { self.0 => getProcessID() }
}
pub fn set_process_id(&self, id: u64) {
with! { self.0 => setProcessID(id) }
}
pub fn now(&self) -> f64 {
with! { self.0 => now() }
}
pub fn rnd(&self) -> u32 {
with! { self.0 => rnd() }
}
pub fn get_option<T>(&self, name: &str) -> Option<T>
where
T: FromStr,
{
self.get_option_raw(name)
.and_then(|value| value.parse::<T>().ok())
}
fn get_option_raw(&self, name: &str) -> Option<String> {
let null = "";
let name = str_for_c(name);
let default_value = str_for_c(null);
let raw_value = with! {
self.0 => getOption(name.as_ptr(), default_value.as_ptr())
};
let value = str_from_c(raw_value.inner);
with! { raw_value => free() };
if value == null { None } else { Some(value) }
}
pub fn client_id(&self) -> i32 {
with! { self.0 => clientId() }
}
pub fn client_count(&self) -> i32 {
with! { self.0 => clientCount() }
}
pub fn shared_random_number(&self) -> i64 {
with! { self.0 => sharedRandomNumber() }
}
pub fn delay(
&self,
duration: Duration,
) -> impl std::future::Future<Output = fdb::FdbResult<()>> + Send + Sync + 'static + use<> {
let f = with! { self.0 => delay(duration.as_secs_f64()) };
fdb::future::FdbFuture::new(f as *mut _)
}
}
impl Promise {
pub(crate) fn new(raw: FDBPromise) -> Self {
Self(raw)
}
pub fn send(self, value: bool) {
with! { self.0 => send(value) };
}
}
impl Drop for Promise {
fn drop(&mut self) {
with! { self.0 => free() };
}
}
impl Metrics {
pub(crate) fn new(raw: FDBMetrics) -> Self {
Self(raw)
}
pub fn reserve(&mut self, n: usize) {
with! { self.0 => reserve(n as i32) }
}
pub fn push(&mut self, metric: Metric) {
let key_storage = str_for_c(metric.key);
let fmt_storage = str_for_c(metric.fmt.unwrap_or("%.3g"));
with! {
self.0 => push(FDBMetric {
key: key_storage.as_ptr(),
fmt: fmt_storage.as_ptr(),
val: metric.val,
avg: metric.avg,
})
}
}
pub fn extend<'a, T>(&mut self, metrics: T)
where
T: IntoIterator<Item = Metric<'a>>,
{
let metrics = metrics.into_iter();
let (min, max) = metrics.size_hint();
self.reserve(max.unwrap_or(min));
for metric in metrics {
self.push(metric);
}
}
}
impl<'a> Metric<'a> {
pub fn val<V>(key: &'a str, val: V) -> Self
where
V: TryInto<f64>,
{
Self {
key,
val: val.try_into().ok().expect("convertion failed"),
avg: false,
fmt: None,
}
}
pub fn avg<V>(key: &'a str, val: V) -> Self
where
V: TryInto<f64>,
{
Self {
key,
val: val.try_into().ok().expect("convertion failed"),
avg: true,
fmt: None,
}
}
}