use std::{fmt, sync::Arc};
use sqlx::postgres::PgArguments;
use sqlx::{Arguments, Encode, Postgres, Type};
use crate::{Error, Result};
pub trait BindValue: Send + Sync + 'static + for<'q> Encode<'q, Postgres> + Type<Postgres> {}
impl<T> BindValue for T where
T: Send + Sync + 'static + for<'q> Encode<'q, Postgres> + Type<Postgres>
{
}
trait ErasedParam: Send + Sync {
fn add_to(&self, args: &mut PgArguments) -> Result<()>;
fn debug_name(&self) -> &'static str;
}
#[derive(Clone)]
#[must_use]
pub struct Param {
inner: Arc<dyn ErasedParam>,
}
impl Param {
pub fn typed<T>(value: T) -> Self
where
T: BindValue,
{
Self {
inner: Arc::new(TypedParam { value }),
}
}
#[inline]
pub fn add_to(&self, args: &mut PgArguments) -> Result<()> {
self.inner.add_to(args)
}
#[inline]
pub fn debug_name(&self) -> &'static str {
self.inner.debug_name()
}
}
impl fmt::Debug for Param {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Param").field(&self.debug_name()).finish()
}
}
#[derive(Clone, Debug, Default)]
#[must_use]
pub struct Params {
params: Vec<Param>,
}
impl Params {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn from_vec(params: Vec<Param>) -> Self {
Self { params }
}
#[inline]
pub fn push(&mut self, param: Param) {
self.params.push(param);
}
pub fn push_typed<T>(&mut self, value: T)
where
T: BindValue,
{
self.push(Param::typed(value));
}
#[inline]
pub fn len(&self) -> usize {
self.params.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.params.is_empty()
}
#[inline]
pub fn as_slice(&self) -> &[Param] {
&self.params
}
pub fn debug_names(&self) -> Vec<&'static str> {
self.params.iter().map(Param::debug_name).collect()
}
pub fn arguments(&self) -> Result<PgArguments> {
let mut args = PgArguments::default();
for param in &self.params {
param.add_to(&mut args)?;
}
Ok(args)
}
}
struct TypedParam<T> {
value: T,
}
impl<T> ErasedParam for TypedParam<T>
where
T: BindValue,
{
fn add_to(&self, args: &mut PgArguments) -> Result<()> {
args.add(&self.value)
.map_err(|error| Error::Encode(error.to_string()))
}
fn debug_name(&self) -> &'static str {
std::any::type_name::<T>()
}
}
#[cfg(test)]
mod tests {
use super::{Param, Params};
#[test]
fn typed_param_reports_the_stored_rust_type_name() {
let param = Param::typed(42_i32);
assert_eq!(param.debug_name(), std::any::type_name::<i32>());
}
#[test]
fn cloned_param_preserves_the_erased_type() {
let param = Param::typed("paid".to_owned());
let cloned = param.clone();
assert_eq!(param.debug_name(), cloned.debug_name());
}
#[test]
fn params_preserve_insertion_order_for_debug_names() {
let mut params = Params::new();
assert!(params.is_empty());
params.push_typed(1_i32);
params.push_typed("two".to_owned());
params.push_typed(true);
assert!(!params.is_empty());
assert_eq!(params.as_slice().len(), 3);
assert_eq!(
params.debug_names(),
vec![
std::any::type_name::<i32>(),
std::any::type_name::<String>(),
std::any::type_name::<bool>(),
]
);
}
#[test]
fn params_arguments_accept_all_stored_values() {
let params = Params::from_vec(vec![
Param::typed(1_i32),
Param::typed("two".to_owned()),
Param::typed(false),
]);
params.arguments().unwrap();
}
#[test]
fn param_debug_prints_the_erased_rust_type_name() {
let param = Param::typed(1_i64);
assert_eq!(
format!("{param:?}"),
format!("Param({:?})", std::any::type_name::<i64>())
);
}
}