#![allow(clippy::module_name_repetitions)]
#![allow(clippy::new_without_default)]
use std::cell::UnsafeCell;
use std::ffi::{c_char, c_uint, c_ulong};
use std::fmt::Debug;
use std::marker::PhantomPinned;
use std::ptr;
use udf_sys::{Item_result, UDF_ARGS, UDF_INIT};
pub use crate::mock_args;
use crate::traits::{Init, Process};
use crate::types::{ArgList, UdfCfg};
use crate::UdfState;
#[derive(Debug)]
pub struct MockUdfCfg {
inner: UnsafeCell<UDF_INIT>,
maxlen_tmp: u64,
_marker: PhantomPinned,
}
impl MockUdfCfg {
#[inline]
pub fn new() -> Self {
Self {
inner: UnsafeCell::new(UDF_INIT {
maybe_null: false,
decimals: 0,
max_length: 0,
ptr: ptr::null_mut(),
const_item: false,
extension: ptr::null_mut(),
}),
maxlen_tmp: 0,
_marker: PhantomPinned,
}
}
#[allow(clippy::useless_conversion)]
pub fn as_init(&mut self) -> &UdfCfg<Init> {
let tmp: c_ulong = self.maxlen_tmp.try_into().unwrap_or_else(|_| {
panic!(
"Max length is limited to c_ulong::MAX, got {}",
self.maxlen_tmp,
)
});
unsafe { (*self.inner.get()).max_length = tmp };
unsafe { UdfCfg::from_raw_ptr(self.inner.get()) }
}
#[allow(clippy::useless_conversion)]
pub fn as_process(&mut self) -> &UdfCfg<Process> {
let tmp: c_ulong = self.maxlen_tmp.try_into().unwrap_or_else(|_| {
panic!(
"Max length is limited to `c_ulong::MAX`, got {}",
self.maxlen_tmp,
)
});
unsafe { (*self.inner.get()).max_length = tmp };
unsafe { UdfCfg::from_raw_ptr(self.inner.get()) }
}
pub fn maybe_null(&mut self) -> &mut bool {
unsafe { &mut (*self.inner.get()).maybe_null }
}
pub fn decimals(&mut self) -> &mut u32 {
unsafe { &mut (*self.inner.get()).decimals }
}
#[allow(clippy::useless_conversion)]
pub fn max_len(&mut self) -> &mut u64 {
self.maxlen_tmp = unsafe { (*self.inner.get()).max_length }.into();
&mut self.maxlen_tmp
}
pub fn is_const(&mut self) -> &mut bool {
unsafe { &mut (*self.inner.get()).const_item }
}
}
#[derive(Debug)]
struct BuiltArgs {
args: Vec<*const c_char>,
lengths: Vec<c_ulong>,
maybe_null: Vec<c_char>,
attributes: Vec<*const c_char>,
attribute_lengths: Vec<c_ulong>,
arg_types: Vec<Item_result>,
}
impl BuiltArgs {
fn new() -> Self {
Self {
args: Vec::new(),
lengths: Vec::new(),
maybe_null: Vec::new(),
attributes: Vec::new(),
attribute_lengths: Vec::new(),
arg_types: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct MockArg {
value: MockArgData,
attribute: String,
maybe_null: bool,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MockArgData {
String(Option<String>),
Bytes(Option<Vec<u8>>),
Real(Option<f64>),
Int(Option<i64>),
Decimal(Option<String>),
}
impl MockArgData {
fn as_item_result(&self) -> Item_result {
match *self {
Self::String(_) | Self::Bytes(_) => Item_result::STRING_RESULT,
Self::Real(_) => Item_result::REAL_RESULT,
Self::Int(_) => Item_result::INT_RESULT,
Self::Decimal(_) => Item_result::DECIMAL_RESULT,
}
}
}
impl From<&str> for MockArgData {
fn from(value: &str) -> Self {
Self::String(Some(value.to_owned()))
}
}
impl From<Option<&str>> for MockArgData {
fn from(value: Option<&str>) -> Self {
Self::String(value.map(std::borrow::ToOwned::to_owned))
}
}
impl From<&[u8]> for MockArgData {
fn from(value: &[u8]) -> Self {
Self::Bytes(Some(value.to_owned()))
}
}
impl From<Option<&[u8]>> for MockArgData {
fn from(value: Option<&[u8]>) -> Self {
Self::Bytes(value.map(std::borrow::ToOwned::to_owned))
}
}
impl From<i64> for MockArgData {
fn from(value: i64) -> Self {
Self::Int(Some(value))
}
}
impl From<Option<i64>> for MockArgData {
fn from(value: Option<i64>) -> Self {
Self::Int(value)
}
}
impl From<f64> for MockArgData {
fn from(value: f64) -> Self {
Self::Real(Some(value))
}
}
impl From<Option<f64>> for MockArgData {
fn from(value: Option<f64>) -> Self {
Self::Real(value)
}
}
impl MockArg {
#[inline]
pub fn new(value: MockArgData, attr: &str, maybe_null: bool) -> Self {
Self {
value,
attribute: attr.to_owned(),
maybe_null,
}
}
#[inline]
pub fn value(&mut self) -> &mut MockArgData {
&mut self.value
}
#[inline]
pub fn attribute(&mut self) -> &mut String {
&mut self.attribute
}
}
#[derive(Debug)]
pub struct MockArgList {
unbuilt_args: Vec<MockArg>,
built_args: Option<BuiltArgs>,
udf_args: Option<UnsafeCell<UDF_ARGS>>,
_pin: PhantomPinned,
}
impl MockArgList {
#[inline]
pub fn new() -> Self {
Self {
unbuilt_args: Vec::new(),
built_args: None,
udf_args: None,
_pin: PhantomPinned,
}
}
#[inline]
pub fn push_arg(&mut self, arg: MockArg) {
self.unbuilt_args.push(arg);
}
#[allow(clippy::pattern_type_mismatch)]
fn build<S: UdfState>(&mut self) -> &ArgList<S> {
let mut building = BuiltArgs::new();
for arg in &self.unbuilt_args {
building
.attributes
.push(arg.attribute.as_str().as_ptr().cast());
building
.attribute_lengths
.push(arg.attribute.len() as c_ulong);
building.maybe_null.push(arg.maybe_null as c_char);
building.arg_types.push(arg.value.as_item_result());
match &arg.value {
MockArgData::String(v) | MockArgData::Decimal(v) => {
let v_ref = v.as_ref();
let buf_ptr = v_ref.map_or(ptr::null(), |s| s.as_ptr().cast());
let len = v.as_ref().map_or(0, |s| s.len() as c_ulong);
building.args.push(buf_ptr);
building.lengths.push(len);
}
MockArgData::Bytes(v) => {
let v_ref = v.as_ref();
let buf_ptr = v_ref.map_or(ptr::null(), |s| s.as_ptr().cast());
let len = v.as_ref().map_or(0, |s| s.len() as c_ulong);
building.args.push(buf_ptr);
building.lengths.push(len);
}
MockArgData::Int(v) => {
let v_ref = v.as_ref();
let data_ptr = v_ref.map_or(ptr::null(), |i| {
let ptr: *const i64 = i;
ptr.cast()
});
building.args.push(data_ptr);
building.lengths.push(0);
}
MockArgData::Real(v) => {
let v_ref = v.as_ref();
let data_ptr = v_ref.map_or(ptr::null(), |i| {
let ptr: *const f64 = i;
ptr.cast()
});
building.args.push(data_ptr);
building.lengths.push(0);
}
}
}
let arg_count = self.unbuilt_args.len();
assert_eq!(building.arg_types.len(), arg_count);
assert_eq!(building.args.len(), arg_count);
assert_eq!(building.lengths.len(), arg_count);
assert_eq!(building.maybe_null.len(), arg_count);
assert_eq!(building.attributes.len(), arg_count);
assert_eq!(building.attribute_lengths.len(), arg_count);
self.built_args = Some(building);
self.set_udf_args();
let udf_args_ref = self.udf_args.as_mut().unwrap().get();
unsafe { ArgList::from_raw_ptr(udf_args_ref) }
}
fn set_udf_args(&mut self) {
let built = self
.built_args
.as_mut()
.expect("Library error: arguments unbuilt");
let udf_args = UDF_ARGS {
arg_count: built.args.len() as c_uint,
arg_types: built.arg_types.as_mut_ptr(),
args: built.args.as_ptr(),
lengths: built.lengths.as_ptr(),
maybe_null: built.maybe_null.as_ptr(),
attributes: built.attributes.as_ptr(),
attribute_lengths: built.attribute_lengths.as_ptr(),
extension: ptr::null(),
};
self.udf_args = Some(UnsafeCell::new(udf_args));
}
pub fn as_init(&mut self) -> &ArgList<Init> {
self.build()
}
pub fn as_process(&mut self) -> &ArgList<Process> {
self.build()
}
}
impl<const N: usize> From<[MockArg; N]> for MockArgList {
fn from(value: [MockArg; N]) -> Self {
let mut ret = Self::new();
for arg in value {
ret.push_arg(arg);
}
ret
}
}
#[macro_export]
macro_rules! mock_args {
(@internal $val:expr, $attr:expr, $nullable:expr) => {
$crate::mock::MockArg::new($crate::mock::MockArgData::from($val), $attr, $nullable)
};
(@internal $type_:ident None, $attr:expr, $nullable:expr) => {
$crate::mock::MockArg::new($crate::mock::MockArgData::$type_(None), $attr, $nullable)
};
(@internal String $val:expr, $attr:expr, $nullable:expr) => {
$crate::mock::MockArg::new($crate::mock::MockArgData::String(Some($val.to_owned())), $attr, $nullable)
};
(@internal Decimal $val:expr, $attr:expr, $nullable:expr) => {
$crate::mock::MockArg::new($crate::mock::MockArgData::Decimal(Some($val.to_owned())), $attr, $nullable)
};
(@internal $type_:ident $val:expr, $attr:expr, $nullable:expr) => {
$crate::mock::MockArg::new($crate::mock::MockArgData::$type_(Some($val)), $attr, $nullable)
};
($( ($( $tt:tt )*) ),* $(,)?) => {
$crate::mock::MockArgList::from([
$(
$crate::mock_args!(@internal $($tt)*)
),*
])
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_args_macro() {
let _args = mock_args![
(1, "1", false),
("some string", "some string", false),
(1000, "value", false),
(Decimal "1.234", "1.234", false),
(Int None, "NULL", false),
(Decimal None, "NULL", false),
(Decimal None, "NULL", true),
];
}
}